题目
一共有n个数,编号是1~n,最开始每个数各自在一个集合中。
现在要进行m个操作,操作共有两种:
“M a b”,将编号为a和b的两个数所在的集合合并,如果两个数已经在同一个集合中,则忽略这个操作;
“Q a b”,询问编号为a和b的两个数是否在同一个集合中;
输入格式
第一行输入整数n和m。
接下来m行,每行包含一个操作指令,指令为“M a b”或“Q a b”中的一种。
输出格式
对于每个询问指令”Q a b”,都要输出一个结果,如果a和b在同一集合内,则输出“Yes”,否则输出“No”。
每个结果占一行。
数据范围
1≤n,m≤10^5
输入样例:
4 5
M 1 2
M 3 4
Q 1 2
Q 1 3
Q 3 4
输出样例:
Yes
No
Yes
概述
并查集主要思想是用树来存储每个集合中的元素,每个集合是一棵树,集合元素是树的节点
集合编号是树的根节点
当合并两个集合时,只要把一棵树直接插到另一棵树的根节点下面
进行并查集操作能有效降低时间复杂度,近乎O(1)
c++ code
#include<iostream>
using namespace std;
const int N = 1e5 + 5;
int f[N];
int find(int x){//找父节点 + 路径优化
if(f[x] != x) f[x] = find(f[x]);//路径优化,非常巧妙的递归
return f[x];
}
int main(){
int n, m;
cin>>n>>m;
for(int i = 1; i <= n; i++) f[i] = i;
while(m--){
char x;
int a, b;
cin>>x>>a>>b;
if(x == 'M') f[find(a)] = find(b);//将一棵树直接插到另一棵树的根节点下
else {
if(find(a) == find(b)) puts("Yes");
else puts("No");
}
}
return 0;
}
在以上路径优化中,例如找到了f【7】= 3,即7的根节点是3,那么下一次寻找f【7】时,显然f【7】 != 7,
因此还会进行递归,但是递归次数只会进行一次,因为我们之前知道了f【7】 = 3,当在递归中找f【3】 时,
因为3已经是根节点了,所以就有f【3】 = 3,因此直接返回。
所以当进行路径优化之后,下一次查找并不会马上就找到,而是还要进行一次递归,这就是为什么O(1)要用‘‘近乎’’这个词
如果不进行路径优化,递归/循环次数会很多