題目大意
一棵樹有 個結點,每個結點上有一種顏色,每個顏色有一個編號,求樹中每個子樹的最多的顏色編號的和。
數據範圍
題解
考慮dsu on tree的過程。
- 遞歸處理輕兒子。
- 遞歸處理重兒子。
- 暴力統計除了重兒子之外的子樹信息,加到桶中。
- 累加答案。
- 如果當前節點 不是 的父節點的重兒子,刪除這顆子樹(包括 的重兒子)對桶的所有貢獻,否則保留信息在桶中,直接返回。
這個過程對於一個已經明白算法運行邏輯的人來說已經很清楚了,但是對不不明白的人來說,仍然比較混亂,而且似乎找不到什麼好的措辭來解釋這部分內容,因此最好的辦法是結合代碼使用,下面的代碼儘量寫得每一行關鍵代碼都有註釋。
理解後我們來考慮這個過程的時間複雜度,對於一個點 ,若其到根節點的路徑上有 條輕邊,則這個點會被計算 次,由樹鏈剖分的知識,我們可以得到,對於任意點 ,恆有 ,因此總時間複雜度不超過
#include<bits/stdc++.h>
using namespace std;
#define maxn 100010
struct edge{
int v,next;
}edges[maxn<<1];
int n,head[maxn];
void ins(int u,int v){
static int cnt=0;
edges[++cnt]=(edge){v,head[u]};
head[u]=cnt;
}
int size[maxn],son[maxn];
void dfs(int u,int fa=-1){
size[u]=1;
for(int i=head[u];i;i=edges[i].next){
int v=edges[i].v;
if(v==fa) continue;
dfs(v,u); size[u]+=size[v];
if(size[son[u]]<size[v]) son[u]=v;
}
}
int c[maxn];//節點的亞瑟
int cnt[maxn];//顏色的出現次數,即桶
int max_cnt;//出現次數最多的顏色的出現次數
int stop;//禁足點,用於標記重兒子
long long sum;//出現次數最多的顏色的編號和
void calc(int u,int fa,int val){
cnt[c[u]]+=val;//累加到桶重
if(max_cnt<cnt[c[u]]) max_cnt=cnt[c[u]],sum=c[u];//找到了出現次數更多的顏色,更新答案
else if(max_cnt==cnt[c[u]]) sum+=c[u];//又出現了一種出現次數一樣多的顏色,累加答案
for(int i=head[u];i;i=edges[i].next){
int v=edges[i].v;
if(v==fa||v==stop) continue;//忽略禁足點和父節點
calc(v,u,val);//暴力遞歸統計答案
}
}
long long ans[maxn];
void work(int u,int fa,int chain){
for(int i=head[u];i;i=edges[i].next){
int v=edges[i].v;
if(v==fa||v==son[u]) continue;//先忽略中兒子和父親
work(v,u,0);//遞歸處理輕兒子
}
if(son[u]){//考慮中兒子
work(son[u],u,1);//遞歸處理重兒子
stop=son[u];//標記重兒子不能在下面的calc函數中計算,因爲在上一行的遞歸中,沒有刪除重兒子桶的貢獻,已經被統計過了。
}
calc(u,fa,1);//暴力統計所有輕兒子的答案
stop=0;//這裏刪除對重兒子的禁足是爲了下面50行的calc函數能把包括重兒子的整顆子樹都刪掉。
ans[u]=sum;//存下這個節點的答案——顏色編號和。
if(chain==0){//如果這是一條重鏈的開頭,意味着其父親和它的連邊爲輕邊,要刪除貢獻
calc(u,fa,-1);//刪除整顆子樹的貢獻
sum=max_cnt=0;//清空統計的信息
}
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&c[i]);
for(int i=1,u,v;i<n;i++){
scanf("%d%d",&u,&v);
ins(u,v);ins(v,u);
} dfs(1); work(1,-1,1);
for(int i=1;i<=n;i++)
printf("%I64d ",ans[i]);
return 0;
}