题目概述
给出一棵
n
个节点的树,每个节点有一个颜色。如果
解题报告
烂大街的dsu on tree(树上启发式合并)经典题。先树剖,对于
x
我们递归求出轻儿子的答案(不记录信息),然后再求出重儿子的答案(记录信息)。之后暴力统计所有子树节点的信息(重儿子为根的子树不需要再统计)求出
因为重儿子留着不删,所以就是父亲以及轻儿子合并到重儿子的过程。由于所在子树大小至少 ×2 ,所以显然每个节点只会被插入 log2n 次,效率 O(nlog2n) 。
示例程序
#include<cstdio>
using namespace std;
typedef long long LL;
const int maxn=100000,maxe=maxn<<1;
int n,col[maxn+5],fa[maxn+5];
int E,lnk[maxn+5],son[maxe+5],nxt[maxe+5];
int si[maxn+5],SH[maxn+5];bool vis[maxn+5];
int MAX,num[maxn+5];LL sum,ans[maxn+5];
#define Add(x,y) son[++E]=y,nxt[E]=lnk[x],lnk[x]=E
void Dfs(int x,int pre=0)
{
si[x]=1;fa[x]=pre;
for (int j=lnk[x];j;j=nxt[j]) if (son[j]!=pre)
{
Dfs(son[j],x);si[x]+=si[son[j]];
if (si[son[j]]>si[SH[x]]) SH[x]=son[j];
}
}
void Update(int x,int f)
{
if ((num[col[x]]+=f)>=MAX) if (num[col[x]]>MAX)
MAX=num[col[x]],sum=col[x]; else sum+=col[x];
for (int j=lnk[x];j;j=nxt[j]) if (son[j]!=fa[x])
if (!vis[son[j]]) Update(son[j],f);
}
void Solve(int x,bool fl=true)
{
for (int j=lnk[x];j;j=nxt[j]) if (son[j]!=fa[x])
if (son[j]!=SH[x]) Solve(son[j],false);
if (SH[x]) Solve(SH[x],true),vis[SH[x]]=true;
Update(x,1);ans[x]=sum;if (SH[x]) vis[SH[x]]=false;
if (!fl) Update(x,-1),MAX=sum=0;
}
int main()
{
freopen("program.in","r",stdin);
freopen("program.out","w",stdout);
scanf("%d",&n);for (int i=1;i<=n;i++) scanf("%d",&col[i]);
for (int i=1,x,y;i<n;i++) scanf("%d%d",&x,&y),Add(x,y),Add(y,x);
Dfs(1);Solve(1);for (int i=1;i<=n;i++) printf("%I64d ",ans[i]);
return 0;
}