一.前言:
这次讲解的dsu on tree,其实是一种优美的暴力,它的时间复杂度分析与树链剖分类似,也需要用到重儿子这一概念(不会树剖的点这里).
这种算法适用于一类树上查询子树的问题,不过它只能处理有子树查询的问题,不支持修改,也不支持链查询.
二.例题:
题目大意:给出一棵树,每个节点有一个颜色x,若当前子树中没有颜色的数量超过了x,则称x是这棵子树中的主导颜色.
现在我们要求每个节点为根的子树的主导颜色的颜色编号的和.
三.题目的分析:
这道题我们先考虑暴力,我们可以从根节点开始扫描,把每一棵子树的答案都暴力得出,时间复杂度O(n^2).
我们换一种思路暴力,我们可以遍历一遍树,当遍历到一个节点时,把它子树的贡献都计入答案,得出答案后再将所有节点的贡献删除.
可是我们可以很显然的看出,其实每个节点最后遍历的儿子子树是不用删除贡献的,所以这样可以做到优化,不过最坏时间复杂度依然是O(n^2).
这个时候我们可以让最后一个遍历的儿子子树尽量大,也就是选择重儿子,这样可以做到稳定O(nlog(n)).
注:这道题其实也可以用线段树合并做,但我当做了dsu on tree的入门题.
四.时间复杂度证明:
我们可以知道,每一个节点当被计入贡献然后删除一遍的时候肯定是在遍历这个节点到根的路径上的一个轻儿子.
然而很明显轻儿子在一个点到根的路径上最多只有log(n)个,所以每个节点遍历一遍总共遍历n个节点,时间复杂度O(nlog(n)).
五.代码实现:
代码如下:
#include<bits/stdc++.h>
using namespace std;
#define Abigail inline void
typedef long long LL;
const int N=100000;
struct node{
int fa,son,size,v;
}d[N+9];
struct side{
int y,next;
}e[N*2+9];
int top,n,cnt[N+9],ma,lin[N+9];
LL ans[N+9],sum;
bool use[N+9];
void ins(int X,int Y){
e[++top].y=Y;
e[top].next=lin[X];
lin[X]=top;
}
void dfs1(int k,int fa){
d[k].fa=fa;
d[k].size=1;
for (int i=lin[k];i;i=e[i].next)
if (e[i].y^fa){
dfs1(e[i].y,k);
d[k].size+=d[e[i].y].size;
if (d[e[i].y].size>d[d[k].son].size) d[k].son=e[i].y;
}
}
void calc(int k,int v){
cnt[d[k].v]+=v;
if (k>0&&cnt[d[k].v]==ma) sum+=d[k].v*1LL;
if (k>0&&cnt[d[k].v]>ma) sum=d[k].v*1LL,ma=cnt[d[k].v];
for (int i=lin[k];i;i=e[i].next){
if (e[i].y==d[k].fa||use[e[i].y]) continue;
calc(e[i].y,v);
}
}
void dfs2(int k){
for (int i=lin[k];i;i=e[i].next)
if (e[i].y^d[k].fa&&e[i].y^d[k].son) dfs2(e[i].y);
if (d[k].son) dfs2(d[k].son),use[d[k].son]=1;
calc(k,1);
ans[k]=sum;
if (d[k].son) use[d[k].son]=0;
if (k^d[d[k].fa].son) calc(k,-1),ma=0,sum=0; //一定要在这里清空,不然这个节点的数据还有用
}
Abigail into(){
scanf("%d",&n);
for (int i=1;i<=n;i++)
scanf("%d",&d[i].v);
int x,y;
for (int i=1;i<n;i++){
scanf("%d%d",&x,&y);
ins(x,y);ins(y,x);
}
}
Abigail work(){
dfs1(1,0);
dfs2(1);
}
Abigail outo(){
for (int i=1;i<n;i++) printf("%I64d ",ans[i]);
printf("%I64d\n",ans[n]);
}
int main(){
into();
work();
outo();
return 0;
}