P2664 树上游戏 题解
题目链接:https://www.luogu.com.cn/problem/P2664
有几篇
在本篇题解中,称节点
首先对求
对于一种颜色
如果第一重循环枚举了一个颜色,那么不管怎样都要遍历整棵树(即使有不遍历的方法也会非常复杂,大概这么觉得吧),时间复杂度就变为了
设当前枚举到了节点
于是,我们能在一遍 dfs 中知道需要给
在最后,
于是我们只需要对于一个
最后
有关这个解法的复杂度,看起来一个一个给
代码实现还有几个不太好处理的细节:
- 如何快速计算
y 的子树内那些如图中c 的子树大小的和,也就是连通块相比y 的子树缺失的部分。为解决这个问题,我们以颜色为下标开一个桶b_x (即代码中的colsiz[x])记录那些极大的、根节点的颜色为x 的子树大小和。在 dfs 到x ,准备递归进入它的一个儿子y 前记录下b_{a_x} 的值(代码中的psiz),在回来时比较一下现在的b_{a_x} 与之前的b_{a_x} ,差就是连通块相比y 的子树缺失的部分的大小。 - 对于上面的
b (colsiz)数组的更新,在每一个儿子y 算出连通块的大小s (代码中的nsiz)时,都将b_{a_x} 加上s 。最后在枚举完儿子后再加1 (节点x )。 - 在打差分标记时,如何找出
y 子树内如图中的那些c 节点打上减的标记。对于每种颜色c 开一个vector(代码中的v[c]),记录下所有(不管是否在y 的子树)根节点颜色为c 且极大的子树的根节点。在需要打标记时,由于刚刚递归进y 的子树,所以y 的子树内那些需要打标记的节点肯定在v_{a_x} 中的最前面。从v_{a_x} 的后面往前面循环,使用 dfs 序判断是否在y 的子树内,每找到一个符合要求的就打上标记并由于它不再极大了,执行pop_back。在循环完所有子节点后,将x 压进v_{a_x} 中即可。 - 在第一遍 dfs 执行完后,对于每种颜色还剩下根节点那儿的一个连通块。利用当前记录下的
colsiz[c],v[c]等信息在主函数内打标记即可,具体见代码。
完整代码:
#include<bits/stdc++.h>
using namespace std;
struct edge
{
int to,nxt;
}e[200005];
int h[100005],a[100005],dfn[100005],siz[100005],colsiz[100005],cnt;
long long cf[100005],dep[100005];
bool buc[100005];
vector<int>v[100005];
inline int read()
{
char c=getchar();
int x=0;
while(c<'0'||c>'9')
c=getchar();
while(c>='0'&&c<='9')
{
x=(x<<3)+(x<<1)+c-'0';
c=getchar();
}
return x;
}
void write(long long x)
{
if(x>9)
write(x/10);
putchar(x%10+'0');
}
inline void adde(int x,int y)
{
e[++cnt].to=y;
e[cnt].nxt=h[x];
h[x]=cnt;
}
void dfs(int x,int f)
{
siz[x]=1;
dfn[x]=++cnt;
for(int i=h[x];i;i=e[i].nxt)
if(e[i].to!=f)
{
int psiz=colsiz[a[x]];//记录下递归前的个数
dfs(e[i].to,x);
siz[x]+=siz[e[i].to];
int nsiz=siz[e[i].to]+psiz-colsiz[a[x]];//此处意为siz[e[i].to]-(colsiz[a[x]]-psiz)
colsiz[a[x]]+=nsiz;
cf[e[i].to]+=nsiz;//打上加的标记
while(v[a[x]].size()&&dfn[v[a[x]].back()]>dfn[x])//从后往前找
{
cf[v[a[x]].back()]-=nsiz;
v[a[x]].pop_back();
}
}
colsiz[a[x]]++;
v[a[x]].push_back(x);
}
void dfs2(int x,int f)//根据差分数组计算最终答案
{
dep[x]=dep[f]+cf[x];
for(int i=h[x];i;i=e[i].nxt)
if(e[i].to!=f)
dfs2(e[i].to,x);
}
int main()
{
int n=read(),m=0,tot=0,i,x,y;
for(i=1;i<=n;i++)
{
a[i]=read();
m=max(m,a[i]);
buc[a[i]]=true;//记录一个颜色是否出现在a[i]中
}
for(i=1;i<n;i++)
{
x=read();
y=read();
adde(x,y);
adde(y,x);
}
cnt=0;
dfs(1,0);
for(i=1;i<=m;i++)//处理包含1的那些连通块
if(buc[i])
{
tot++;
cf[1]+=n-colsiz[i];
for(int j=0;j<v[i].size();j++)
cf[v[i][j]]-=n-colsiz[i];
}
dfs2(1,0);
for(i=1;i<=n;i++)
{
write(1ll*n*tot-dep[i]);
putchar('\n');
}
return 0;
}