原題連接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; }
🤪沒有啦!感謝閱讀!若是以爲寫的還不錯的話,記得點一下右邊的大拇指嗷!~