#include <iostream>
using namespace std;
const int N = 1e6 + 10;
int n, m;
int p[N];
//并查集里的father数组 存的是每个元素他的父节点是谁
int find(int x){//find x 返回x所在集合的编号 即返回x的祖宗节点
if(p[x] != x) p[x] = find(p[x]);
return p[x];
}
int main(){
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++) p[i] = i;
while(m--){
char op[2];
int a, b;
scanf("%s%d%d", op, &a, &b);
if(op[0] == 'M') p[find(a)] = find(b);
else{
if(find(a) == find(b)) puts("Yes");
else puts("No");
}
}
return 0;
}
![[Pasted image 20221220130751.png]]
# 问题 2 的时间复杂度高?如何优化?
并查集的优化:当叶节点从x向上找找到祖宗节点后,直接将所有叶节点指向祖宗节点
即路径压缩(并查集在加完这个优化之后基本上就可以看成O(1)的时间复杂度了)
#include <iostream>
using namespace std;
const int N = 1e6 + 10;
int n, m;
int p[N];
int find(int x){
if(p[x] != x) p[x] = find(p[x]);
return p[x];
}// 返回x的祖宗节点 + 路径压缩
//核心操作:如果x不是根节点的话 就让他的父节点 = 祖宗节点 然后返回他的父节点就可以了
int main(){
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++) p[i] = i;
while(m--){
char op[2];
//为甚么这里是op[2] 因为scanf有缺点:会读入空格和回车之类莫名其妙的字符 但是scanf读入字符串的时候会自动忽略空格和回车
//因此如果用scanf读入一个字母的话建议读入成字符串的形式 因为这样可以帮助我们过滤掉空格和回车
int a, b;
scanf("%s%d%d", op, &a, &b);
if(op[0] == 'M') p[find(a)] = find(b);//让p[a]的父节点即a树直接插到b树下面
else{
if(find(a) == find(b)) puts("Yes");//判断两个节点是否在同一个集合里面 //因为find(a)返回的是a树的祖宗节点 //find(b)返回的是b树的祖宗节点
//如果两个祖宗节点一样的话就说明在一个集合里面否则就不在
else puts("No");
}
}
return 0;
}