题目描述
给定一个包含n个点(编号为1~n)的无向图,初始时图中没有边。
现在要进行m个操作,操作共有三种:
“C a b”,在点a和点b之间连一条边,a和b可能相等;
“Q1 a b”,询问点a和点b是否在同一个连通块中,a和b可能相等;
“Q2 a”,询问点a所在连通块中点的数量;
输入格式
第一行输入整数n和m。
接下来m行,每行包含一个操作指令,指令为“C a b”,“Q1 a b”或“Q2 a”中的一种。
输出格式
对于每个询问指令”Q1 a b”,如果a和b在同一个连通块中,则输出“Yes”,否则输出“No”。
对于每个询问指令“Q2 a”,输出一个整数表示点a所在连通块中点的数量
每个结果占一行。
数据范围
1≤n,m≤105
输入样例:
5 5
C 1 2
Q1 1 2
Q2 1
C 2 5
Q2 5
输出样例:
Yes
2
3
算法 并查集
连通块: 从a可以走到b , 从b可以走到a ,那就说明这两个在一个连通块中
C a b :如果a、b相等,那就是a这个点(或者b)形成一个环。
思路:
用并查集存储每个集合,多开一个cnt存储集合里面的结点数量。
详解代码注释
参考文献
y总讲解视频
C++ 代码
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 100010;
//p存储当前下标的父结点 cnt存储当前下标集合里面的数量
int p[N] , cnt[N];
int n , m ;
//返回x的根结点,包含路径压缩
int find(int x){
if(p[x] != x) p[x] = find(p[x]);
return p[x];
}
int main(){
cin>>n>>m;
//初始化开始的集合,每个集合里面的根结点编号就是自己的大小
for(int i = 1 ; i <= n ; i++){
p[i] = i;
//每个集合里面一开始的数量也就只有一个
cnt[i] = 1;
}
while(m--){
//op操作的字符数组
char op[5];
//a b 两个遥操作具体的数
int a , b;
scanf("%s%d%d" , op , &a , &b);
//两个点连一条边,也就是两个集合进行合并
if(op[0] == 'C'){
scanf("%d%d" , &a , &b);
//对a b进行特判,如果两个数值一样,下面的操作就不需要再进行了
//因为a b一样就是同一个数在一个集合里面,或者两个不同的数再同一个集合里面,就不需要再进行cnt的更新
if(find(a) == find(b)) continue;
//更新连通块中结点的数量
/*规定对于cnt,我们只保证根结点的cnt是有意义的
如果说,两个集合a ,b,要把a集合插入到b集合中去,那么此时b集合根结点的cnt就要
进行更新cnt[b] += cnt[a] .
*/
cnt[find(b)] += cnt[find(a)];
p[find(a)] = find(b);
}else if(op[1] == '1'){//Q1 询问两个点是否在同一集合当中
scanf("%d%d" , &a , &b);
//根结点都相同, 就是在一个集合中
if(find(a) == find(b)) puts("Yes");
//否则不在
else puts("No");
}else{//最后一种情况,询问元素所在连通块中的结点数量
scanf("%d" , &a);
//直接返回a的根结点记录的结点数量即可
printf("%d\n" , cnt[find(a)]);
}
}
return 0;
}