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;
}
posted @ 2020-11-04 19:35  quanjun  阅读(58)  评论(0编辑  收藏  举报