原题连接ios
题目:数组
一共有n个数,编号是1~n,最开始每一个数各自在一个集合中。优化
如今要进行m个操做,操做共有两种:spa
“M a b”,将编号为a和b的两个数所在的集合合并,若是两个数已经在同一个集合中,则忽略这个操做;
“Q a b”,询问编号为a和b的两个数是否在同一个集合中;code
输入格式ci
第一行输入整数n和m。get
接下来m行,每行包含一个操做指令,指令为“M a b”或“Q a b”中的一种。io
输出格式stream
对于每一个询问指令”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
首先,咱们要知道并查集能够作哪些操做?
并查集能够将两个集合进行合并(并);
并查集能够判断两个元素是否在同一个集合当中(查)。
基本原理: 咱们将每一个集合用一棵树(不必定是二叉树)来表示,这棵树的树根的编号就是整个集合的编号。而每个节点都存储它本身自己的父节点,在这题中咱们用 p[x] 这么一个数组来存储每个节点(x)的父节点。
Q & A :
Q1 : 如何判断该节点是否为树根节点?(如下的 p[] 数组均存储的是节点的父节点)
A1 :除了根节点以外的点,p [x] != x ,因此若是 p [x] = x 的话,那么 x 必定就为这棵树的根节点。 对应代码就是:
bool is_root(){ if(p[x] == x) return true; else return false; }
Q2 :如何求 x 的集合编号?
A2 :当 p [x] 存储的节点不等于 x 的时候,那么此时的 x 必定不是根节点,这时咱们就继续往上面的父节点找,直到找到根节点为止,对应代码就是这样的:
int findRoot(){ while(p[x] != x) x = p[x]; return x; }
Q3 : 如何合并两个集合?
A3 :假设 p[x] 为 x 的集合 1 的编号, p[y] 是 y 的集合 2 编号。此时咱们只须要让集合 1 的根节点连上集合 2 的根节点便可。对应到代码大概讲长这样: p[集合 1 根节点] = 集合 2 根节点
若是要实现以上这些步骤,其实时间复杂度仍是挺高的,这时咱们就要引入一个新的话题,如何将并查集问题进行优化呢?
并查集的优化:路径压缩
如何解释路径压缩?
我的理解: 假设,当 x 所在的这个节点,经过千辛万苦终于找到了它的根节点,那么咱们就让 x 这个节点在找根节点时通过的全部的父节点都会直接指向根节点上。
说了这么多,下面就正式开始进入敲代码环节吧!
首先,咱们须要把 p[] 这个存储每一个节点的父节点的数组给初始化了,咱们让每一个节点的父节点都指向本身也就是:
for(int i = 0; i < n; i++) p[i] = i;
第二步,就是开始写咱们的并查集最最核心的操做了——寻找根节点,同时,咱们须要在寻找根节点的时候,加上并查集的路径压缩,对并查集作优化。
int findRoot(int x){ if(p[x] != x) p[x] = find(p[x]); //若是当前 x 不是根节点,咱们就让他的父节点去找根节点,这就在寻找根节点的同时作了路径压缩优化了(妙啊~~~~~) return p[x]; }
最后,附上完整AC代码:
#include <iostream> #include <cstdio> using namespace std; const int N = 100010; int p[N]; int n, m; int findRoot(int x){ if(p[x] != x) p[x] = findRoot(p[x]); return p[x]; } int main(){ cin >> 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[findRoot(a)] = findRoot(b); else{ if(findRoot(a) == findRoot(b)) cout << "Yes" << endl; else cout << "No" << endl; } } return 0; }
🤪没有啦!感谢阅读!若是以为写的还不错的话,记得点一下右边的大拇指嗷!~