树上启发式合并-附有例题CF600E

树上启发式合并(dsu on tree)

但这个 dsu 应该和并查集没啥关系

总体思想就是利用重链剖分的性质,将小堆(轻儿子)往大堆(重儿子)合并,这样子可以达到 log 的复杂度
以下是几个重要函数

  1. void dfs1(int u, int father){} 得到一系列重要数组,其中包括 son[]
  2. void dfs2(int u, int op){} 计算 u 子树的答案
  3. add_ans(int u, int xson){} 将 u 子树除了 xson 子树的所有加入答案,是合并轻子树的操作
  4. del_ans(int u){} 将 u 子树的答案删除,清空 cnt 数组,用于第一遍 dfs2 后轻子树的删除操作
    第一次敲的时候 add_ans 函数敲成了 add 函数......
int n, m;
ll col[N];

int head[N], cnt;
struct Edge{
    int from, to, nxt;
}e[N << 1];
void add(int u, int v){
    e[++cnt].from = u;
    e[cnt].to = v;
    e[cnt].nxt = head[u];
    head[u] = cnt;
}

int deep[N], fa[N], siz[N], son[N];

void dfs1(int u, int father){
    fa[u] = father;
    deep[u] = deep[father] + 1;
    siz[u] = 1;
    for(int i = head[u]; i != 0; i = e[i].nxt){
        int v = e[i].to;
        if(v == father) continue;
        dfs1(v, u);
        siz[u] += siz[v];
        if(!son[u] || siz[son[u]] < siz[v]) son[u] = v;
    }
}

ll tmpans, ans_cnt, num[N], ans[N];
void add_ans(int u, int xson){
    num[col[u]]++;
    if(num[col[u]] == ans_cnt){
        tmpans += col[u];
    }else if(num[col[u]] > ans_cnt){
        ans_cnt = num[col[u]];
        tmpans = col[u];
    }
    for(int i = head[u]; i != 0; i = e[i].nxt){
        int v = e[i].to;
        if(v == xson || v == fa[u]) continue;
        add_ans(v, xson);
    }
}
void del_ans(int u){
    num[col[u]]--;
    for(int i = head[u]; i != 0; i = e[i].nxt){
        int v = e[i].to;
        if(v == fa[u]) continue;
        del_ans(v);
    }
}

void dfs2(int u, int op){
    for(int i = head[u]; i != 0; i = e[i].nxt){
        int v = e[i].to;
        if(v == fa[u] || v == son[u]) continue;
        dfs2(v, 0);
    }
    if(son[u] != 0){
        dfs2(son[u], 1);
    }
    add_ans(u, son[u]);
    ans[u] = tmpans;
    if(op == 0){
        tmpans = ans_cnt = 0;
        del_ans(u);
    }
}

再补一个 main 函数

点击查看代码
void solve() {
    cin >> n;
    for(int i = 1; i <= n; i++){
        cin >> col[i];
    }
    for(int i = 1; i < n; i++){
        int u, v;
        cin >> u >> v;
        add(u, v);
        add(v, u);
    }
    dfs1(1, 0);
    dfs2(1, 1);
    for(int i = 1; i <= n; i++){
        cout << ans[i] << " ";
    }
}

Lomsat gelral

题面翻译

  • 有一棵 \(n\) 个结点的以 \(1\) 号结点为根的有根树
  • 每个结点都有一个颜色,颜色是以编号表示的, \(i\) 号结点的颜色编号为 \(c_i\)
  • 如果一种颜色在以 \(x\) 为根的子树内出现次数最多,称其在以 \(x\) 为根的子树中占主导地位。显然,同一子树中可能有多种颜色占主导地位。
  • 你的任务是对于每一个 \(i\in[1,n]\),求出以 \(i\) 为根的子树中,占主导地位的颜色的编号和。
  • \(n\le 10^5,c_i\le n\)

题目描述

You are given a rooted tree with root in vertex $ 1 $ . Each vertex is coloured in some colour.

Let's call colour $ c $ dominating in the subtree of vertex $ v $ if there are no other colours that appear in the subtree of vertex $ v $ more times than colour $ c $ . So it's possible that two or more colours will be dominating in the subtree of some vertex.

The subtree of vertex $ v $ is the vertex $ v $ and all other vertices that contains vertex $ v $ in each path to the root.

For each vertex $ v $ find the sum of all dominating colours in the subtree of vertex $ v $ .

输入格式

The first line contains integer $ n $ ( $ 1<=n<=10^{5} $ ) — the number of vertices in the tree.

The second line contains $ n $ integers $ c_{i} $ ( $ 1<=c_{i}<=n $ ), $ c_{i} $ — the colour of the $ i $ -th vertex.

Each of the next $ n-1 $ lines contains two integers $ x_{j},y_{j} $ ( $ 1<=x_{j},y_{j}<=n $ ) — the edge of the tree. The first vertex is the root of the tree.

输出格式

Print $ n $ integers — the sums of dominating colours for each vertex.

样例 #1

样例输入 #1

4
1 2 3 4
1 2
2 3
2 4

样例输出 #1

10 9 3 4

样例 #2

样例输入 #2

15
1 2 3 1 2 3 3 1 1 3 2 2 1 2 3
1 2
1 3
1 4
1 14
1 15
2 5
2 6
2 7
3 8
3 9
3 10
4 11
4 12
4 13

样例输出 #2

6 5 4 3 2 3 3 1 1 3 2 2 1 2 3

posted @ 2024-06-28 23:52  9102700  阅读(26)  评论(0)    收藏  举报