题目链接:https://codeforces.com/contest/600/problem/E
题目大意:
求每一棵子树各自的出现最多次的颜色编号的和, n ≤ 1 e 5 n≤1e5 n≤1e5
题目思路:
解决的最简单方法自然是直接暴力,但是可以发现对于1e5的范围无能为力。所以这里要引入一种新的算法,针对不带修改的子树问题有着独特的优势。
dsu是并查集的意思,但是这个算法跟并查集没有一点关系,唯一有点搭边的是并查集的一种优化方法,就是每次都让小的部分并入大的来缩短时间。
首先照搬树链剖分的概念,重儿子就是最大子树的根节点,dsu的思想就是,先暴力算所有轻儿子的情况,每个轻儿子算完都得清空自己的贡献,因为前一个孩子的计数会影响后面的孩子,算完后最后计算重孩子的情况。因为重孩子已经是最后一个处理的孩子了,剩下来的就是整个子树的大家族,也就是自己本身,加上重孩子和所有轻孩子的情况,所以重孩子是需要的一部分,不需要再删掉,然后暴力再把轻孩子和自己算上即可。时间复杂度的节省就在少算了一次重孩子上,根据大佬的复杂度推算,是 O ( n l o g n ) O(nlogn) O(nlogn)的。
代码中cnt记录当前统计情况,dfs1处理每个点的重儿子,dfs2正式计算。flag是指是否删除当前贡献,1是删除,0不删除。首先先计算轻儿子,所以遇到重儿子跳过。这时候算的轻儿子需要去掉贡献。然后如果有重儿子的话就处理重儿子,重儿子的情况保留,同时更新Son,跟后面用来更新的add说一声现在的重儿子是Son,遇到它不用算了已经算过了。然后后面add的最后一个参数就是val,就是加或者删。加完孩子后就得到了当前点代表的子树的情况。Son变成0,因为如果要删当前的情况的话,得无差别全部删光,所以得把Son变成不会遍历到的点,才能保证全部删掉,否则重儿子的情况就被保留了。
以下是代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define rep(i,a,b) for(ll i=a;i<=b;i++)
#define per(i,a,b) for(ll i=a;i>=b;i--)
const ll MAXN =2e5+5;
const ll MAXM = 4e7+5;
const ll MOD = 998244353;
int n,c[MAXN],siz[MAXN],son[MAXN],cnt[MAXN],x,y;
ll ans[MAXN];
vector<int>v[MAXN];
void dfs1(int u,int fa){
siz[u]=1,son[u]=0;
int len=v[u].size();
rep(i,0,len-1){
int y=v[u][i];
if(y==fa)continue;
dfs1(y,u);
siz[u]+=siz[y];
if(siz[y]>siz[son[u]]){
son[u]=y;
}
}
}
int Son,maxx;
ll sum;
void add(int u,int fa,int val){
cnt[c[u]]+=val;
if(cnt[c[u]]>maxx){
maxx=cnt[c[u]];
sum=c[u];
}
else if(cnt[c[u]]==maxx){
sum+=c[u];
}
int len=v[u].size();
rep(i,0,len-1){
int y=v[u][i];
if(y==fa||y==Son)continue;
add(y,u,val);
}
}
void dfs2(int u,int fa,int flag){
int len=v[u].size();
rep(i,0,len-1){
int y=v[u][i];
if(y==fa||y==son[u])continue;
dfs2(y,u,1);
}
if(son[u])dfs2(son[u],u,0),Son=son[u];
add(u,fa,1);
Son=0;
ans[u]=sum;
if(flag)add(u,fa,-1),sum=maxx=0;
}
int main()
{
while(cin>>n){
memset(cnt,0,sizeof(cnt));
Son=maxx=sum=0;
rep(i,1,n)cin>>c[i];
rep(i,1,n)v[i].clear();
rep(i,1,n-1){
cin>>x>>y;
v[x].push_back(y);
v[y].push_back(x);
}
dfs1(1,0);
dfs2(1,0,0);
rep(i,1,n){
cout<<ans[i]<<" ";
}cout<<endl;
}
}