题目描述
给定一个包含 n 个点(编号为 1~n )的无向图,初始时图中没有边。
现在要进行 m 个操作,操作共有三种:
- C a b,在点 a 和点 b 之间连一条边,a 和 b 可能相等;
- Q1 a b,询问点 a 和点 b 是否在同一个连通块中,a 和 b 可能相等;
- Q2 a,询问点 a 所在连通块中点的数量。
数据范围
1 <= n,m <= 1e5
输入
第一行输入一个整数 n 和 m。
接下来 m 行,每行包含一个操作指令,指令为 C a b ,Q1 a b 或 Q2 a 中的一种。
输出
对于每个询问命令 Q1 a b,如果 a 和 b 在同一连通块中,则输出 Yes,否则输出 No。
对于每个询问指令 Q2 a,输出一个整数表示点 a 所在连通块中点数量。
每个结果占一行。
样例输入
5 5 C 1 2 Q1 1 2 Q2 1 C 2 5 Q2 5
样例输出
Yes 2
题目分析:
在输入时有两个数n和m,由题可知n表示一个从1到n之中所有数组成的数组是 f[n]。
由于1 <= n,m <= 1e5 ,可知数组会比较大,所以要用全局变量来定义数组。
而m表示下面对这个数组有m个操作。
再联系上面所说,可以得出m个操作又分为3个种类,
第一个操作是:C a b,在点 a 和点 b 之间连一条边,a 和 b 可能相等。
操作分析:从这个我们可以看出用到了并查集的思想,把b与a的根节点相连
第二个操作是:Q1 a b,询问点 a 和点 b 是否在同一个连通块中,a 和 b 可能相等。
操作分析:这一个则是让你找出a和b的根节点,通过判断根节点是否相同来判断a和b是否在同一个连通块中。若相同,则表示在,若不同,则表示不在。
第三个操作是:Q2 a,询问点 a 所在连通块中点的数量。
操作分析:这个操作一时间我们并看不出来要具体怎么操作,仔细考虑后,我们有一个更为简单的操作:就是另外设置一个与上面f[n]大小相同的数组s[n],用来存放元素个数,因为在最开始操作的时候每个边上只有一个元素,未相互之间进行连接,所以需要给s[n]中的每一个数赋初值为1。当数与数进行连同或者数与一个连通块内的某个数连同时,若连通块的根节点或两个数中的其中一个数作为根节点为f[i],则直接将s[i]++。当要连接的两个数分别是两个连通块内的数时,我们可以先默认其中一个数的根节点是最终的根节点,假设这个根节点是f[i],另外一个数所在连通块的根节点为f[j],则我们直接将s[i]+=s[j],然后这个s[i]的值就表示的是两个连通块连同后内部的全部元素。,最后,我们在每次只要找到这个数所在连通块的根节点下标就可以直接通过s[n]数组找出这个连通块内的所有元素。
内容补充:
有一些同学并查集寻找根节点的方法并不熟练,下面的代码是寻找根节点并压缩路径的函数,以供大家参考:
int find(int a)
{
if(a!=f[a]) f[a]=find(f[a]);
return f[a];
}
参考代码:
(同学们尽量自己进行写代码,不会后再来观看)
#include<bits/stdc++.h>
using namespace std;
int f[100000], si[100000];
int find(int a)
{
if (a != f[a]) f[a] = find(f[a]);
return f[a];
}
int main()
{
int n, m;
cin >> n >> m;
for (int i = 0; i < n; i++)
{
f[i] = i;
si[i] = 1;
}
while (m--)
{
string q;
int x, y;
cin >> q;
if (q == "C")
{
cin >> x >> y;
int ny = find(y);
int nx = find(x);
if (nx != ny)
{
f[nx] = ny;
si[ny] += si[nx];
}
}
else if (q == "Q2")
{
cin >> x;
y = find(x);
cout << si[y] << endl;
}
else if (q == "Q1")
{
cin >> x >> y;
if (find(x) == find(y))
cout << "Yes" << endl;
else
cout << "No" << endl;
}
}
return 0;
}
若有疑问,欢迎大家来评论补充,我看见后会回复。