借鉴博客:
【学习笔记】树上启发式合并 / DSU on Tree
树上启发式合并
dsu on tree学习笔记
思路:
暴力:复杂度
对于每个节点,暴力遍历子树,将它们的数据统计出来得到当前节点的答案,然后再暴力将这棵子树的数据清空,以免影响到别的节点。
首先考虑为什么要把子树的数据清空(会占用很多时间):
对于节点 ,在做子树答案时是相互独立的,由于空间的限制,我们不可能对于每一个节点开一个数组来记录数据,只能开一个全局数组。所以在做下一个结点的答案时,如果上个结点的数据没有清空会导致结果出错。
但是我们发现只是子树的结果互相影响,而做节点 为根的树的答案是要用到子树的数据的,所以最后一个子树的数据没必要清空。
最大化统计时的最后一个节点 轻重链剖分优化:复杂度
对于节点 ,可以在做子树答案时保留最后一棵子树 的数据不清空,然后统计 的答案时绕过 节点统计别的子树。那么 选哪个呢?当然是选子树大小 最大的。
于是就可以运用重儿子进行算法优化。
统计每种颜色出现的次数,维护单个颜色出现的最高次数,维护出现次数最高的颜色的编号之和
满足要求的颜色编号之和会爆
MyCode:
#include<bits/stdc++.h> using namespace std; const int maxn=1e5+7,maxm=2e5+7; typedef long long ll; inline ll read(){ ll s = 0, w = 1; char ch = getchar(); while (ch < 48 || ch > 57) { if (ch == '-') w = -1; ch = getchar(); } while (ch >= 48 && ch <= 57) s = (s << 1) + (s << 3) + (ch ^ 48), ch = getchar(); return s * w; } int head[maxn],Next[maxm],to[maxm],tot; void add(int x,int y) { to[++tot]=y; Next[tot]=head[x]; head[x]=tot; } int size[maxn],son[maxn]; int col[maxn],pos; ll ans[maxn],cnt[maxn],sum; bool vis[maxn]; void dfs1(int x,int f) { size[x]=1; for(int i=head[x],v;i;i=Next[i]) { v=to[i]; if(v==f) continue; dfs1(v,x); size[x]+=size[v]; if(size[son[x]]<size[v]) son[x]=v; } } void calc(int x,int f) { cnt[col[x]]+=1; if(cnt[col[x]]>pos) { pos=cnt[col[x]]; sum=col[x]; } else if(cnt[col[x]]==pos) sum+=col[x]; for(int i=head[x],v;i;i=Next[i]) { v=to[i]; if(v==f||vis[v]) continue; calc(v,x); } } void delet(int x,int f) { cnt[col[x]]-=1; for(int i=head[x],v;i;i=Next[i]) { v=to[i]; if(v==f) continue; delet(v,x); } } void dfs2(int x,int f,bool opt) { for(int i=head[x],v;i;i=Next[i]) { v=to[i]; if(v==f||son[x]==v) continue; dfs2(v,x,false); } if(son[x]) { dfs2(son[x],x,true); vis[son[x]]=true; } calc(x,f); ans[x]=sum; if(son[x]) vis[son[x]]=false; if(!opt) { delet(x,f); pos=sum=0; } } int main() { int n=read(); for(int i=1;i<=n;++i) col[i]=read(); for(int i=2,u,v;i<=n;++i) { u=read(),v=read(); add(u,v); add(v,u); } dfs1(1,0); dfs2(1,0,false); for(int i=1;i<=n;++i) printf("%lld ",ans[i]); return 0; }