题意;
每条路径的权值是:路径上经过的不同颜色节点的个数。
询问各个点互达后的路径权值总和
思路: 看了小半天才看懂。。。
首先解释下官方题解的意思。
对于所求的问题,可以转化为每种颜色对于答案可以贡献多少次值(颜色i对于在所有路径中出现了几次)。
颜色i贡献次数=所有路径-路径中未出现颜色i的路径条数。
之后把所有的颜色i相加即可。
代码中;siz表示以当前节点为根,子树大小
sum表示以颜色i为节点的,所有子树大小
每次在dfs时,因先遍历其子树,所以sum[col[u]] 有可能会增加,而与之前未增加时候(第一次到u) 的sum[col[u]]做差就是在v节点中,以颜色col[u]为根的子树的总大小。
tmp=siz[v]-sonu 就是在v子树中 除u颜色之外的节点个数。
ans在这时减去tmp的组合就是在此不通过col[u]节点的连通块中的边的条数。
之后在main函数时 ans减去的值是有可能某些节点在颜色i的父亲等较高位置,因此sum[i]无法统计到,这是上层的连通块数量也是颜色i无法贡献的,所以也应该减去!!
#include <bits/stdc++.h>
using namespace std;
const int maxn=2e5+5;
vector< int >vec[maxn];
int siz[maxn];
int vis[maxn];
int col[maxn];
int sum[maxn];
typedef long long ll;
ll ans,n;
void dfs(int u,int fa)
{
siz[u]=1;
int pre=sum[col[u]];
int add=0;
for(int i=0;i<vec[u].size();i++)
{
int v=vec[u][i];
if(v==fa)
continue;
dfs(v,u);
siz[u]+=siz[v];
ll sonu=sum[col[u]]-pre;//表示在子树v中,以颜色col[u]为根的子树总大小
ll tmp=siz[v]-sonu; //表示在子树v中,除了u颜色之外的节点个数
add+=sonu; //add记录当前所有子树中以col[u]为根的节点总数
ans-=tmp*(tmp-1)/2; //表示v子树中,各个节点互相到达,无需进过col[u]
pre=sum[col[u]]; //表示记录上一次的sum
}
sum[col[u]]+=siz[u]-add;//sum[col[u]]加上以u为根,属于这个节点的连通块的节点总数
}
int main()
{
int cs=1;
while(~scanf("%lld",&n))
{
memset(sum,0,sizeof(sum));
memset(col,0,sizeof(col));
memset(siz,0,sizeof(siz));
memset(vis,0,sizeof(vis));
int cnt=0;
for(int i=1;i<=n;i++)
{
scanf("%d",&col[i]);
if(!vis[col[i]])
vis[col[i]]=1,cnt++;
}
int u,v;
for(int i=1;i<=n;i++)
vec[i].clear();
for(int i=1;i<n;i++)
{
scanf("%d%d",&u,&v);
vec[u].push_back(v);
vec[v].push_back(u);
}
ans=cnt*(n)*(n-1)/2;
dfs(1,0);
for(int i=1;i<=n;i++)
{
if(!sum[i])
continue;
ll res=n-sum[i];
ans-=(res-1)*res/2;//此步骤减去的是 在i颜色节点的 上面部分,有n-sum[i]个节点,这些节点互通不需要经过i颜色节点。
}
printf("Case #%d: %lld\n", cs++, ans);
}
}
/*
2
1 2
1 2
*/