【學習筆記/題解】樹上啟發式合併/CF600E Lomsat gelral
阿新 • • 發佈:2020-10-05
\(\text{Solution:}\)
樹上啟發式合併,是對普通暴力的一種優化。
考慮本題,最暴力的做法顯然是暴力統計每一次的子樹,為了避免其他子樹影響,每次統計完子樹都需要清空其資訊。
但是,如果我們先對非\(x\)的節點進行統計,最後統計\(x\)然後合併其他節點的資訊,那麼,\(x\)的統計資訊就沒有必要被刪掉。
那麼顯然地,\(x\)的子樹越大越好。
於是,自然想到輕重鏈剖分,並將\(x\)設定為其重兒子。於是,演算法模型如下:
-
對所有非重兒子進行統計並清空其所記錄的統計資訊。
-
對重兒子進行統計並保留其資訊。
-
暴力將其他兒子的資訊合併到重兒子上,得到當前子樹的資訊。
根據樹鏈剖分的性質,一個點到根的路徑上的輕邊條數不超過\(\log n\)條,而一個節點只有其祖先遇到輕邊的時候才會被統計一次。
所以複雜度為\(O(n\log n).\)
關於這題 直接安裝上述演算法流程進行暴力統計即可。
關於一點對樹剖性質的證明:每次經過一條輕邊,其子樹大小最少會變成原來的一半,所以輕邊條數是\(O(\log n)\)的。
#include<bits/stdc++.h> using namespace std; const int MAXN=3e5+10; typedef long long ll; int son[MAXN],head[MAXN],n,tot,siz[MAXN]; int vis[MAXN],cnt[MAXN],col[MAXN],Mx,Son; vector<int>v[MAXN]; ll sum,ans[MAXN]; void dfs(int x,int fa){ siz[x]=1; for(int i=0;i<v[x].size();++i){ int j=v[x][i]; if(j==fa)continue; dfs(j,x);siz[x]+=siz[j]; if(siz[j]>siz[son[x]])son[x]=j; } } void add(int x,int fa,int val){ cnt[col[x]]+=val; if(cnt[col[x]]>Mx)Mx=cnt[col[x]],sum=col[x]; else if(cnt[col[x]]==Mx)sum+=col[x]*1ll; for(int i=0;i<v[x].size();++i){ int j=v[x][i]; if(j==fa||j==Son)continue; add(j,x,val); } } void dfs2(int x,int fa,int opt){ for(int i=0;i<v[x].size();++i){ int j=v[x][i]; if(j==fa)continue; if(j!=son[x])dfs2(j,x,0); } if(son[x])dfs2(son[x],x,1),Son=son[x]; add(x,fa,1);Son=0; ans[x]=sum; if(!opt)add(x,fa,-1),sum=Mx=0; } int main(){ scanf("%d",&n); for(int i=1;i<=n;++i)scanf("%d",col+i); for(int i=1;i<n;++i){ int x,y; scanf("%d%d",&x,&y); v[x].push_back(y);v[y].push_back(x); } dfs(1,0);dfs2(1,0,0); for(int i=1;i<=n;++i)printf("%I64d ",ans[i]); puts(""); return 0; }