1. 程式人生 > 其它 >[考試總結]ZROI-21-NOIP衝刺-TEST9 總結

[考試總結]ZROI-21-NOIP衝刺-TEST9 總結

ZROI-21-NOIP衝刺-TEST9 總結

#T1 紅黑樹

Time Limit: 1s | Memory Limit: 512MiB

#題意簡述

給定一棵 \(n(n\leq10^6)\) 個節點的樹,每個節點有紅、黑兩種顏色,每次可以將顏色相同的連通塊染為另一種顏色,問將整棵樹變為同種顏色的最少步數。

#大體思路

不難發現初始狀態下相同顏色的點的變化必然是同步的,所以我們可以將一個顏色是相同的連通塊合併為一個點,得到一棵新的樹,然後我們發現我們最優的策略必然是從中心開始,一點一點地將整棵樹變為同種顏色,不難發現這樣所需的操作次數是 \(\left\lfloor\frac {len} 2\right\rfloor\),其中 \(len\)

為新的樹的直徑,證明可以考慮 \(len\) 為奇數、偶數兩種情況分開討論。時間複雜度為 \(O(n)\).

#Code

#define ll long long
#define mset(l, x) memset(l, x, sizeof(l))

const int N = 2000010;
const int INF = 0x3fffffff;

template <typename T> inline void read(T &x) {
    x = 0; int f = 1; char c = getchar();
    for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
    for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
    x *= f;
}

template <typename T> inline T Max(T a, T b) {return a > b ? a : b;}
template <typename T> inline T Min(T a, T b) {return a < b ? a : b;}

struct Edge {int u, v, nxt;} e[N], ne[N];
struct DSU {
    int fa[N], siz[N], num;

    inline void init(int x) {
        num = x;
        for (int i = 1; i <= num; ++ i)
          fa[i] = i, siz[i] = 1;
    }
    
    inline int find(int x) {
        while (x != fa[x]) x = fa[x] = fa[fa[x]]; return x;
    }

    inline void merge(int x, int y) {
        x = find(x), y = find(y);
        if (x == y) return;
        if (siz[x] > siz[y]) swap(x, y);
        siz[y] += siz[x], fa[x] = y;
    }
} dsu;

int n, col[N], head[N], ecnt(1), ncnt(1), nhead[N], vis[N], d[N];

inline void add_edge(int u, int v) {
    e[ecnt].u = u, e[ecnt].v = v;
    e[ecnt].nxt = head[u], head[u] = ecnt ++;
}

inline void connect(int u, int v) {
    ne[ncnt].u = u, ne[ncnt].v = v;
    ne[ncnt].nxt = nhead[u], nhead[u] = ncnt ++;

    ne[ncnt].u = v, ne[ncnt].v = u;
    ne[ncnt].nxt = nhead[v], nhead[v] = ncnt ++;
}

void combine(int x, int fa) {
    vis[x] = 1;
    for (int i = head[x]; i; i = e[i].nxt) {
        if (e[i].v == fa) continue;
        if (col[e[i].v] == col[x])
          dsu.merge(e[i].v, x), combine(e[i].v, x);
    }
}

void build(int x, int fa) {
    for (int i = head[x]; i; i = e[i].nxt) {
        if (e[i].v == fa) continue;
        if (col[e[i].v] != col[x])
          connect(dsu.find(x), dsu.find(e[i].v));
        build(e[i].v, x);
    }
}

int res;

void dfs(int x, int fa) {
    d[x] = d[fa] + 1;
    for (int i = nhead[x]; i; i = ne[i].nxt) {
        if (ne[i].v == fa) continue;
        dfs(ne[i].v, x);
    }
    if (d[x] > d[res]) res = x;
}

int main() {
    read(n); dsu.init(n);
    for (int i = 1; i <= n; ++ i) {
        char c; cin >> c; 
        if (c == 'R') col[i] = 1;
        else col[i] = 0;
    }
    for (int i = 1; i < n; ++ i) {
        int u, v; read(u), read(v);
        add_edge(u, v); add_edge(v, u);
    }
    for (int i = 1; i <= n; ++ i)
      if (!vis[i]) combine(i, 0);
    int st = 0, ed = 0;
    build(1, 0); dfs(dsu.find(1), 0);
    mset(d, 0); st = res; res = 0;
    dfs(dsu.find(st), 0); ed = res;
    printf("%d", d[ed] / 2);
    return 0;
}

#T2 操作序列

Time Limit: 1s | Memory Limit: 512MiB

#題意簡述

給出一個長度為 \(k(k\leq10^5)\) 的數列,然後給出 \(n(n\leq10^5)\) 個操作:

操作分為三種:

  1. \(a_i=b\)
  2. \(a_i\) 加上 \(b\)
  3. \(a_i\) 乘上 \(b\)

其中 \(i,b\) 是給定的,每個操作只能用一次,最多使用 \(m(m\leq10^5)\) 個操作,讓整個數列的乘積最大,給出最大乘積模 \(10^9+7\) 後的結果。

#大體思路

顯然對於同一個位置,賦值操作只會使用一次,然後我們便可以將勝出的賦值操作轉化為加法操作,然後對於加法操作,我們一定是先加貢獻大的那個,之後便可以把所有的加法操作轉化為乘法操作,注意到每一個加法操作一定是在相同位置上一步加法操作的基礎上轉化,得到新的權值——採用此操作可以使答案怎加多少倍。

之後將所有的乘法操作按照新的權值排序即可。

#Code

#define ll long long
#define pbk(x) push_back(x)

const int N = 100010;
const ll MOD = 1e9 + 7;
const int INF = 0x3fffffff;

template <typename T> inline void read(T &x) {
    x = 0; int f = 1; char c = getchar();
    for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
    for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
    x *= f;
}

struct Node {
    int id; double val;
    
    inline Node() {}
    inline Node(int _id, double _val) {id = _id, val = _val;}
};
struct action {int op, pos; ll val;} p[N];

int k, n, m; ll a[N], ans = 1, cov[N];

vector <Node> add, mul, v[N];
vector <int> fnl;

inline double calc(int pos, double val) {
    return (1.0 * a[pos] + val) / (1.0 * a[pos]);
}

inline bool cmp(Node x, Node y) {return x.val > y.val;}

inline bool cmp2(int x, int y) {
    if (p[x].pos != p[y].pos) return p[x].pos < p[y].pos;
    else if (p[x].op != p[y].op) return p[x].op < p[y].op;
    else return p[x].val < p[y].val;
}

int main() {
    read(k), read(n), read(m);
    for (int i = 1; i <= k; ++ i) read(a[i]);
    for (int i = 1; i <= n; ++ i) {
        int op, pos; ll val;
        read(op), read(pos), read(val);
        p[i].op = op, p[i].pos = pos, p[i].val = val;
        if (op == 1) {
            if (!cov[pos]) cov[pos] = i;
            else if (p[cov[pos]].val < val)
              cov[pos] = i;
        }
        else if (op == 2) v[pos].pbk(Node(i, 1.0 * val));
        else mul.pbk(Node(i, 1.0 * val));
    }
    for (int i = 1; i <= k; ++ i) if (cov[i])
      v[i].pbk(Node(cov[i], 1.0 * (p[cov[i]].val - a[i])));
    for (int i = 1; i <= k; ++ i) sort(v[i].begin(), v[i].end(), cmp);
    for (int i = 1; i <= k; ++ i) {
        double sum = 1.0 * a[i];
        for (auto x : v[i]) {
            mul.pbk(Node(x.id, (sum + x.val) / sum));
            sum += x.val;
        }
    }
    sort(mul.begin(), mul.end(), cmp);
    for (int i = 0; i < m && i < mul.size(); ++ i) fnl.pbk(mul[i].id);
    sort(fnl.begin(), fnl.end(), cmp2);
    for (auto x : fnl) {
        if (p[x].op == 1) a[p[x].pos] = max(a[p[x].pos], p[x].val);
        else if (p[x].op == 2) (a[p[x].pos] += p[x].val) %= MOD;
        else (a[p[x].pos] *= p[x].val) %= MOD;
    }
    for (int i = 1; i <= k; ++ i) (ans *= a[i]) %= MOD;
    printf("%lld", ans); return 0;
}

#T3 吃零食

Time Limit: 1s | Memory Limit: 256MiB

#題意簡述

對於 \(n(n\leq10^6)\) 的一個排列 \(p_i\),它的貢獻是 \(s=\sum(i\bmod p_i)\),現在已知 \(s(s\leq10^{18})\),請給出一組合法的 \(p_i\),若無解則輸出 SPFA is dead!

#大體思路

首先,顯然 \(s\) 的上界是

\[\sum_{i=0}^{n-1}i=\dfrac{n\cdot(n-1)}2 \]

然後,考慮進行如下構造:

最初,\(a_n=1,a_i=i+1(i\in\{2,3,\dots,n-1\})\),顯然這樣的貢獻和是 \(S=\frac{n\cdot(n-1)}2\),我們考慮 \(\Delta s=S-s\),從大到小列舉 \(i\),對於 \(i\leq\Delta s\),我們每次交換 \(a_i\)\(a_{i-1}\),顯然這樣會使 \(\Delta s\) 減小 \(i\),可以用歸納法證明,這樣可以得到

\[[3,\dfrac {n\cdot(n-1)}2 - 2]\cup\{\dfrac{n\cdot(n - 1)}2\} \]

中的所有數,剩下的部分單獨特殊構造即可。

#Code

#define ll long long

const int N = 2000010;
const int INF = 0x3fffffff;

template <typename T> inline void read(T &x) {
    x = 0; int f = 1; char c = getchar();
    for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
    for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
    x *= f;
}

ll n, s, a[N];

int main() {
    read(n), read(s);
    if (s == 0) {
        for (int i = 1; i <= n; ++ i)
          printf("%d\n", i);
        return 0;
    } else if (s == 1) {
        if (n < 2) printf("SPFA is dead!\n");
        else {
            printf("2\n1\n");
            for (int i = 3; i <= n; ++ i)
              printf("%d\n", i);
        }
        
    } else if (s == 2) {
        if (n < 3) printf("SPFA is dead!\n");
        else {
            printf("3\n1\n2\n");
            for (int i = 4; i <= n; ++ i)
              printf("%d\n", i);
        }
    } else {
        if (s > n * (n - 1) / 2) {puts("SPFA is dead!\n"); return 0;}
        ll rest = n * (n - 1) / 2 - s;
        for (int i = 1; i < n; ++ i) a[i] = i + 1; a[n] = 1;
        if (rest == 1) {
            if (n & 1) a[1] = 3, a[2] = 1, a[n] = 2;
            else swap(a[1], a[n]);
        } else {
            for (int i = n - 1; i; -- i) if (rest >= i)
              rest -= i, swap(a[i], a[i - 1]);
        }
        for (int i = 1; i <= n; ++ i) printf("%lld ", a[i]);
    }
    return 0;
}

#T4 數樹

Time Limit: 3s | Memory Limit: 512MiB

咕咕咕~