推荐这篇文章,这里只是简介
主要解决像 统计树上一个节点的子树中具有某种特征的节点数 这种问题。
我们以codeforces 600 E为例
对于这种问题,最简单的方法就是每次暴力统计其子树,然后再暴力删去数据。复杂度 O(n2) 。
很明显,暴力跑得慢的原因就是它多删、统计了很多次。
每个节点建一棵平衡树,每次保留最大的子树,把小的子树合并到大的上去。复杂度 O(nlog2n) ,可以卡过这道题。
暴力消去轻儿子的影响,每次保留重儿子。根据树剖的性质可以知道每次复杂度是 O(logn) 的,因此总复杂度为 O(nlogn)
代码:
#include
#include
#include
#include
#define N 100005
using namespace std;
typedef long long LL;
struct edge{
int next,to;
}ed[N<<1];
int n,m,k,co,fa[N],sz[N],to[N],c[N],h[N],num[N];
//fa[],sz[],to[]是树剖的各种量,h[]为邻接表,c[]为颜色,num[]为该颜色出现的次数
LL ans[N],sum;
bool v[N];
inline char readc(){
static char buf[100000],*l=buf,*r=buf;
if (l==r) r=(l=buf)+fread(buf,1,100000,stdin);
if (l==r) return EOF; return *l++;
}
inline int _read(){
int x=0; char ch=readc();
while (!isdigit(ch)) ch=readc();
while(isdigit(ch)) x=x*10+ch-48,ch=readc();
return x;
}
void addedge(int x,int y){
ed[++k].next=h[x],ed[k].to=y,h[x]=k;
}
void dfs1(int x){//第一次DFS把重儿子求出来
sz[x]=1;
for (int i=h[x];i;i=ed[i].next)
if (ed[i].to!=fa[x]){
int v=ed[i].to;
fa[v]=x,dfs1(v),sz[x]+=sz[v];
if (sz[to[x]]void nsrt(int x,int f){//统计并修改
num[c[x]]+=f;
if (f>0&&num[c[x]]>=co){
//如果当前颜色次数大于等于现在的颜色
//这道题等于的也要统计进去
if (num[c[x]]>co) sum=0,co=num[c[x]];//如果比当前的大了需要重新计算
sum+=c[x];
}
for (int i=h[x];i;i=ed[i].next)
if (ed[i].to!=fa[x]&&!v[ed[i].to])
nsrt(ed[i].to,f);
}
void dfs2(int x,bool f){
//第二次DFS统计答案
//f表示这次统计是否保留
for (int i=h[x];i;i=ed[i].next)
if (ed[i].to!=fa[x]&&ed[i].to!=to[x])
dfs2(ed[i].to,0);//优先处理轻儿子
if (to[x]) dfs2(to[x],1),v[to[x]]=1;
//接着处理重儿子,v[]表示是否需要统计
nsrt(x,1),ans[x]=sum;//统计并修改影响
if (to[x]) v[to[x]]=0;
if (!f) nsrt(x,-1),co=sum=0;//不保留要改回来
}
int main(){
n=_read();
for (int i=1;i<=n;i++) c[i]=_read();
for (int i=1;iint u=_read(),v=_read();
addedge(u,v),addedge(v,u);
}
dfs1(1),dfs2(1,0);
for (int i=1;i<=n;i++) printf("%I64d ",ans[i]);
return 0;
}