2020牛客寒假算法基础集训营1 F maki和tree 【并查集】

题目描述

有一天,maki拿到了一颗树。所谓树,即没有自环、重边和回路的无向连通图。
这个树有 n 个顶点,n-1 条边。每个顶点被染成了白色或者黑色。
maki想知道,取两个不同的点,它们的简单路径上有且仅有一个黑色点的取法有多少?
注:
①树上两点简单路径指连接两点的最短路。
② <p,q> 和 <q,p> 的取法视为同一种。

输入描述 

第一行一个正整数 n 。代表顶点数量。(1\leq n\leq 100000)
第二行是一个仅由字符'B'和'W'组成的字符串。第 i 个字符是B代表第 i 个点是黑色,W代表第 i 个点是白色。
接下来的 n-1 行,每行两个正整数 x,y,代表 x 点和 y 点有一条边相连。(1\leq x,y\leq n)

输出描述

一个正整数,表示只经过一个黑色点的路径数量。

示例输入 

3
WBW
1 2
2 3

示例输出 

3

分析:

 只经过一个黑色点的路径有两种情况:

1. 黑色点在路径端点。

2. 黑色点在路径中间。

而这两种情况实际上都要求维护白色点的组,即不经过黑色点能够直接相互到达的白色点构成一组。

对于第一种情况,每个黑色点所涉及到的路径为,与该点相连的所有白点组中白点的个数之和  \small \sum_{i = 1}^{n}x_{i}

对于第二种情况,每个黑色点涉及到的路径为,与该点相连的所有白点组中,任意两个组的白点个数相乘再求和 \small \sum_{i = 1}^{n}\sum_{j = i + 1}^{n}x_{i} * x_{j}∑ i=1 n ​ x i

用并查集维护白色点的组,并维护每个组中白色点的个数。

具体解释见代码。

#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;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值