dsu on tree 学习笔记
dsu on tree 学习笔记
引入
dsu 是并查集的缩写,然鹅本算法和并查集没啥关系。当然,dsu on tree 也有中文名字:树上启发式合并。也就是说,这个算法是用于处理一些树上信息的合并的。
dsu on tree 和莫队一样,都是优雅的暴力。优雅是因为思想很优雅,暴力是因为所有修改都是暴力做的——没错,遍历整棵子树的那种暴力。
思想
当然,虽然是暴力,直接搞肯定会 T。既然是要合并信息,我们肯定是要能少删除就少删除,也就是说,保留信息最多的,让其他信息往上合并。
dsu on tree 利用了重链剖分的一个性质来实现这一点。我们每次都先处理轻儿子,再把轻儿子的信息删除,然后再处理重儿子,保留重儿子信息的同时再去暴力把轻儿子的信息求出来,以达到求出这一棵子树中所有节点答案的目的。
看着很暴力,但是实际上,这样做的复杂度只有
例题
模板题,具体见代码注释。
#include<bits/stdc++.h> using namespace std; const int N = 1e5+100; inline int read(){ int x = 0; char ch = getchar(); while(ch<'0' || ch>'9') ch = getchar(); while(ch>='0'&&ch<='9') x = x*10+ch-48, ch = getchar(); return x; } int head[N], tot; struct node{ int nxt, to; }edge[N<<1]; void add(int u, int v){ edge[++tot].nxt = head[u]; edge[tot].to = v; head[u] = tot; } int col[N]; int n; long long vis[N]; long long sum_ans, now_ans; long long ans[N]; int siz[N], son[N]; void dfs1(int u, int fath){//和树链剖分的dfs1基本一样,求出重儿子 siz[u] = 1; for(int i = head[u]; i; i = edge[i].nxt){ int v = edge[i].to; if(v == fath) continue; dfs1(v, u); siz[u]+=siz[v]; if(siz[son[u]]<siz[v]) son[u] = v; } } void update(int u, int fath){//增加信息,更新答案 vis[col[u]]++; if(vis[col[u]]>sum_ans) now_ans =col[u], sum_ans = vis[col[u]]; else if(vis[col[u]]==sum_ans) now_ans+=col[u];//可能有不止一种优势颜色 for(int i = head[u]; i; i = edge[i].nxt){ int v = edge[i].to; if(v == fath){ continue; } update(v, u); } } void del(int u, int fath){//删除信息,清空轻儿子的贡献 vis[col[u]]--; for(int i = head[u]; i; i = edge[i].nxt){ int v = edge[i].to; if(v == fath){ continue; } del(v, u); } } void dfs(int u, int fath, bool op){ for(int i = head[u]; i; i = edge[i].nxt){ int v = edge[i].to; if((fath ^ v)&&(son[u] ^ v)){ dfs(v, u, 1); }//先搞轻儿子 } if(son[u]) dfs(son[u], u, 0);//最后搞重儿子 vis[col[u]]++;//注意当前节点的贡献不要忘了加上 if(vis[col[u]]>sum_ans) now_ans =col[u], sum_ans = vis[col[u]]; else if(vis[col[u]]==sum_ans) now_ans+=col[u]; for(int i = head[u]; i; i = edge[i].nxt){//此时重儿子已经处理好且信息未删除,现在把轻儿子的贡献合并上来 int v = edge[i].to; if((v ^ fath) && (v ^ son[u])){ update(v, u); } } ans[u] = now_ans;//记录答案 if(op) now_ans = sum_ans = 0, del(u, fath);//如果是轻儿子就清空当前子树的贡献 } int main(){ n = read(); for(int i = 1; i<=n; ++i) col[i] = read(); for(int i = 1; i<n; ++i){ int u = read(), v = read(); add(u, v); add(v, u); } dfs1(1, 0); dfs(1, 0, 0); for(int i = 1; i<=n; ++i){ printf("%lld", ans[i]); if(i<n) putchar(' '); else putchar('\n'); } return 0; }