1. 程式人生 > 實用技巧 >CF600E Lomsat gelral 題解 樹上啟發式合併

CF600E Lomsat gelral 題解 樹上啟發式合併

題目連結:https://codeforces.com/problemset/problem/600/E

題目大意:求一個以 \(1\) 為根節點的有根樹中每個節點對應的子樹中出現次數最多的所有顏色的編號之和。

解題思路:

樹上啟發式合併。

額外的處理(如何在 \(O(1)\) 時間內求出節點 \(u\) 當前對應的出現次數最多的節點編號之和):

  • \(res[u]\) 表示 \(u\) 對應的答案
  • \(ap[i]\) 表示目前出現次數等於 \(i\) 的顏色編號和
  • \(apid\) 表示目前出現次數最多的顏色的出現次數

示例程式碼:

#include <bits/stdc++.h>
using namespace std;
const int maxn = 100010;
int n, m, sz[maxn], c[maxn], cnt[maxn];
bool big[maxn];
vector<int> g[maxn];

void getsz(int u, int p) {
    sz[u] ++;
    for (auto v: g[u])
        if (v != p)
            getsz(v, u), sz[u] += sz[v];
}

long long res[maxn], ap[maxn];
int apid;

void add(int u, int p, int x) {

    int cc = cnt[ c[u] ];
    ap[cc] -= c[u];
    ap[cc+x] += c[u];
    if (x > 0 && apid < cc+x) apid = cc+x;
    if (x < 0 && cc == apid && ap[cc] == 0) apid --;

    cnt[ c[u] ] += x;
    for (auto v: g[u])
        if (v != p && !big[v])
            add(v, u, x);
}
void dfs(int u, int p, bool keep) {
    int mx = -1, bigSon = -1;   // mx表示重兒子的sz, bigSon表示重兒子編號
    for (auto v: g[u])
        if (v != p && sz[v] > mx)
            mx = sz[ bigSon = v ];
    for (auto v: g[u])
        if (v != p && v != bigSon)
            dfs(v, u, false);

    if (bigSon != -1)
        dfs(bigSon, u, true),
        big[bigSon] = true;
    add(u, p, 1);
    assert(apid > 0);
    res[u] = ap[apid];
    if (bigSon != -1)
        big[bigSon] = 0;
    if (!keep)
        add(u, p, -1);
}
int main() {
    cin >> n;
    for (int i = 1; i <= n; i ++) cin >> c[i];
    for (int i = 1; i < n; i ++) {
        int a, b;
        cin >> a >> b;
        g[a].push_back(b);
        g[b].push_back(a);
    }
    getsz(1, -1);
    dfs(1, -1, false);
    for (int i = 1; i <= n; i ++) cout << res[i] << " ";
    return 0;
}