题目描述
有一天,maki拿到了一颗树。所谓树,即没有自环、重边和回路的无向连通图。
这个树有 n 个顶点,n-1 条边。每个顶点被染成了白色或者黑色。
maki想知道,取两个不同的点,它们的简单路径上有且仅有一个黑色点的取法有多少?
注:
①树上两点简单路径指连接两点的最短路。
② <p,q> 和 <q,p> 的取法视为同一种。
输入描述
第一行一个正整数 n 。代表顶点数量。
第二行是一个仅由字符'B'和'W'组成的字符串。第 i 个字符是B代表第 i 个点是黑色,W代表第 i 个点是白色。
接下来的 n-1 行,每行两个正整数 x,y,代表 x 点和 y 点有一条边相连。
输出描述
一个正整数,表示只经过一个黑色点的路径数量。
示例输入
3
WBW
1 2
2 3
示例输出
3
分析:
只经过一个黑色点的路径有两种情况:
1. 黑色点在路径端点。
2. 黑色点在路径中间。
而这两种情况实际上都要求维护白色点的组,即不经过黑色点能够直接相互到达的白色点构成一组。
对于第一种情况,每个黑色点所涉及到的路径为,与该点相连的所有白点组中白点的个数之和 ;
对于第二种情况,每个黑色点涉及到的路径为,与该点相连的所有白点组中,任意两个组的白点个数相乘再求和
用并查集维护白色点的组,并维护每个组中白色点的个数。
具体解释见代码。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <vector>
#include <map>
using namespace std;
typedef long long ll;
const int maxn = 100000 + 5;
int n;
int fa[maxn],sum[maxn];
char str[maxn];
vector<int> v[maxn];//记录与黑色点相连的白色点
vector<int> black;//记录黑色点
map<int,int> mp;
int find(int x){
if(fa[x]==x) return x;
int root=find(fa[x]);
sum[x]+=sum[fa[x]]; //路径压缩过程中,组内白点个数的更新
return fa[x]=root;
}
void merge(int a,int b){//两组合并
int ffa=find(a),ffb=find(b);
if(ffa==ffb) return;
fa[ffb]=ffa;
sum[ffa]+=sum[ffb];
}
void init(){
for(int i=0;i<=n;i++){
fa[i]=i;
sum[i]=1;//初始化每个白点自己成为一组
}
}
int main() {
scanf("%d",&n);
scanf("%s",str+1);
for(int i=1;i<=n;i++){
if(str[i]=='B'){
mp[i]=1;
black.push_back(i);
}
}
init();
int x,y;
for(int i=1;i<n;i++){
scanf("%d%d",&x,&y);
if(mp[x]&&!mp[y]){
v[x].push_back(y);
}
else if(!mp[x]&&mp[y]){
v[y].push_back(x);
}
else if(!mp[x]&&!mp[y]){//两个白点相连,合并两组
merge(x,y);
}
}
ll ans=0;
for(int i=0;i<black.size();i++){//对每个黑点进行计算
ll cnt=0;//记录所有与该黑点相连组的白点总数,简化第二种情况下的内层循环
for(int j=0;j<v[black[i]].size();j++){
int ff1=find(v[black[i]][j]);
ans=ans+sum[ff1];
cnt+=sum[ff1];
}
for(int j=0;j<v[black[i]].size();j++){
int ff1=find(v[black[i]][j]);
cnt-=sum[ff1];
ans=ans+cnt*sum[ff1];
}
}
printf("%lld\n",ans);
return 0;
}