题解 P2664 【树上游戏】
My Blog
点分治的神仙题哇天哪,一个个题解看得我那叫一个懵。我还是看神仙的题解才懂的,我这篇题解希望能让您们理解神仙的做法。
首先瞅一眼数据范围
那么点分怎么搞呢?说来就不简单啊
那么这样一来就有问题如下:
-
- 当前节点
i 到根节点路径上的颜色的贡献如何处理。
我们先解决
如何减去当前处理的子树对 我也觉得很蠢。
第二个问题稍稍难想一点,设当前分治的子树为
接着,如果统计的过程中遇到了一个新的颜色,那么
- 判断点
i 的颜色是否出现过,若没有,则tot+=cnt[col[i]],num+=size[root]-size[now] 。 -
ans[i]+=sum-tot+num
然后分治递归处理更多的子树就完了!
不理解可以看下代码(码风仙,无空格,不过有注释)
Code
#include<iostream>
#include<cstdio>
using namespace std;
int n,col[100001];
int rt,sum,top,Y,maxs[100001],size[100001],now[100001],cbook[100001],cnt[100001];
int head[100001],nx[200001],to[200001];
bool vis[100001],Book[100001];
long long Sum,ans[100001];
void add(int u,int v,int d)
{
to[d]=v,nx[d]=head[u];
head[u]=d;
}
void getrt(int x,int fa)
{
size[x]=1,maxs[x]=0;
for(int i=head[x];i;i=nx[i])
if(to[i]!=fa&&!vis[to[i]])
{
getrt(to[i],x);
size[x]+=size[to[i]];
maxs[x]=max(maxs[x],size[to[i]]);
}
maxs[x]=max(maxs[x],sum-size[x]);
if(maxs[x]<maxs[rt])rt=x;
}
void getsize(int x,int fa)
{
size[x]=1;
for(int i=head[x];i;i=nx[i])
if(to[i]!=fa&&!vis[to[i]])
getsize(to[i],x),size[x]+=size[to[i]];
}
void getcol(int x,int fa)
{
if(!now[col[x]])
{
cnt[col[x]]+=size[x];
Sum+=size[x];
}//如果没出现过,我们的贡献就 +=size
if(!Book[col[x]])cbook[++top]=col[x],Book[col[x]]=true;//记录一下当前分治的部分总共有哪些颜色
now[col[x]]++;//出现次数变更
for(int i=head[x];i;i=nx[i])
if(to[i]!=fa&&!vis[to[i]])
getcol(to[i],x);
now[col[x]]--;
}
void delcol(int x,int fa)
{
if(!now[col[x]])
{
cnt[col[x]]-=size[x];
Sum-=size[x];
}
now[col[x]]++;
for(int i=head[x];i;i=nx[i])
if(to[i]!=fa&&!vis[to[i]])
delcol(to[i],x);
now[col[x]]--;
}
void Count(int x,int fa,int num,long long tot)
{
if(!now[col[x]])num++,tot+=cnt[col[x]];//如果此颜色首次出现,辣么记录 tot 贡献
now[col[x]]++;
ans[x]+=Sum-tot+num*Y;//ans的处理
for(int i=head[x];i;i=nx[i])
if(to[i]!=fa&&!vis[to[i]])
Count(to[i],x,num,tot);
now[col[x]]--;
}
void work(int x)
{
getsize(x,0);//先把 size 处理出来
Sum=0,top=0;
getcol(x,0);//统计所有子树的 cnt 数组
for(int i=head[x];i;i=nx[i])
if(!vis[to[i]])
{
now[col[x]]++,delcol(to[i],x),cnt[col[x]]-=size[to[i]],Sum-=size[to[i]];//先减去当前子树的贡献,各种减就对了
Y=size[x]-size[to[i]],Count(to[i],x,0,0);//Y 就是其他子树的节点数
getcol(to[i],x),now[col[x]]--,cnt[col[x]]+=size[to[i]],Sum+=size[to[i]];//再把贡献加回来QwQ
}
ans[x]+=Sum-cnt[col[x]]+size[x];
for(int i=1;i<=top;i++)//将出现过的颜色的贡献统统删掉
Book[cbook[i]]=false,cnt[cbook[i]]=0;
}
void solve(int x)
{
vis[x]=true;
work(x);//开始计算贡献
for(int i=head[x];i;i=nx[i])
if(!vis[to[i]])
{
rt=0,sum=size[to[i]];
getrt(to[i],x);
solve(rt);
}
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)scanf("%d",&col[i]);
int u,v;
for(int i=1;i<n;i++)
{
scanf("%d%d",&u,&v);
add(u,v,i);
add(v,u,i+n);
}
maxs[rt=0]=sum=n;
getrt(1,0);//求重心
solve(rt);
for(int i=1;i<=n;i++)
printf("%lld\n",ans[i]);
}