並查集與帶權並查集---由淺入深


並查集 


 基本概念

​ 並查集,在一些有N個元素的集合應用問題中,我們通常是在開始時讓每個元素構成一個單元素的集合,然后按一定順序將屬於同一組的元素所在的集合合並,其間要反復查找一個元素在哪個集合中。

​ 並查集是一種樹型的數據結構,用於處理一些不相交集合(Disjoint Sets)的合並及查詢問題。常常在使用中以森林來表示。

實現原理

​ 通過更新維護父親節點使得,合並后的集合最終擁有同一個點根節點,擁有相同根節點即為同類。

  • Search 查找自己的根節點;(紅圈標記為根節點)

  

  • Merge 合並兩個節點在一個集合;(假設尋找合並節點5和2)

  

  • 壓縮路徑;壓縮路徑可以使得在多次查詢時,查詢時間得到優化,具體過程是優化其結構,使得查詢點的父親節點為根節點。(上圖壓縮路徑后得到)  

  

代碼實現

 1 void init(){  // 初始化自己祖先就是自己
 2     for(int i = 1 ; i<= n; i++){
 3         pre[i] = i;
 4     }
 5 }
 6 
 7 int Search(int x){  // 遞歸尋找自己的祖先
 8     return x == pre[x] ? x : pre[x] = Search(pre[x]);
 9 }
10 
11 void Merge(int x, int y){ // 合並兩個節點
12     int fx = Search(x);
13     int fy = Search(y);
14     if(fx != fy)  pre[fx] = fy; // 把x合並到y即把x祖先設置為y的祖先
15 }
View Code

帶權並查集


基本概念

​ 帶權並查集即是結點存有權值信息的並查集;當兩個元素之間的關系可以量化,並且關系可以合並時,可以使用帶權並查集來維護元素之間的關系;帶權並查集每個元素的權通常描述其與並查集中祖先的關系,這種關系如何合並,路徑壓縮時就如何壓縮;帶權並查集可以推算集合內點的關系,而一般並查集只能判斷屬於某個集合。

經典例題

食物鏈(FJUTOJ2022 & POJ1182)

傳送門:FJUTOJ2022 && POJ1182

題意:

動物王國中有三類動物A,B,CABBCCA
現有N個動物,以1-N編號。每個動物都是A,B,C中的一種,但是我們並不知道它到底是哪一種。
用兩種說法對這N個動物所構成的食物鏈關系進行描述:

  • "1 X Y",表示XY是同類。
  • "2 X Y",表示XY

給出K句話,有些是真的,有些是假的,滿足下列任一條件即為假話,否則是真話:
1) 當前的話與前面的某些真的話沖突,就是假話;
2) 當前的話中XYN大,就是假話;
3) 當前的話表示XX,就是假話。

輸出假話的數量;

解題思路:

​ 這個題目需要維護推算集合內部的關系,所以可以利用帶權並查集解決。

​ 創建利用pre數組和rela數組判斷集合關系,pre判斷集合之間的關系,rela判斷集合內部元素的關系,這題我們可以建立三種關系同類,捕食,和被捕食三種關系,我們在rela數組中分別用0,1,2表示:

  1.  0表示和根節點是同類關系
  2.  1表示和跟節點是捕食關系(吃根節點)
  3.  2表示和根節點是被捕食關系(被根節點吃)

​ 確定表示了三種關系表示,剩下是需要維護的關系,我們需要維護些什么關系呢?

​ 首先是合並考慮壓縮路徑時的關系維護,我們壓縮路徑時已知B和A的關系,以及A和A根節點的關系,需要推導出B和A根節點的關系,如圖是我們橙色線是我們要推導出的關系,黑色線是以知關系。

  

我們列舉所有情況在表格中來看,是否存在某種關系。

結點A與根關系 結點B與A關系 B與根關系
0 0 0
0 1 1
0 2 2
1 0 1
1 1 2
1 2 0
2 0 2
2 1 0
2 2 1

從表格中我們顯然可以得到關系` rela[b] = (rela[a] + rela[b]) % 3`壓縮路徑關系的代碼如下。

1 int Find(int x){   // 查找當前結點的根節點
2     if(x == pre[x]) return x;
3     else{       // 壓縮路徑
4         int temp = pre[x];
5         pre[x] = Find(pre[x]); // 遞歸尋找頭根點,壓縮路徑節點
6         rela[x] = (rela[x] + rela[temp]) % 3; // 壓縮路徑關系
7     }
8     return pre[x];
9 }
View Code

​ 然后我們考慮關系的查找,我們以及知道A和B在同一集合,即代表他們根節點相同,我們要確定兩者之間的關系,我們還是線畫出關系圖,橙色線是我們要推導出的關系,黑色線是以知關系。

  

我們同樣在表格中寫出對應關系


從表格中可以得到關系`relation[a->b] = (rela[a] - rela[b]) % 3`,減法可能會產生負數,所以要先+3再進行取模,查找關系的代碼如下

1 if(Find(x) == Find(y)){ // 如果兩個根節點相同
2         relation = (rela[x] - rela[y] + 3) % 3; // 推出兩個根節點之間的關系
3         return relation == r; // 判斷給出關系是否與已經存在的關系矛盾
4 }
View Code
結點A與根關系 結點B與根關系 A與B關系
0 0 0
0 1 2
0 2 1
1 0 1
1 1 0
1 2 2
2 0 2
2 1 1
2 2 0

​ 最后我們考慮合並兩個節點時關系的維護,我們已經知a和其根節點的關系,以及b和其根節點的關系,當我們把b集合合並到a集合時,我們需要考慮b根節點和a根節點存在的關系,關系圖如下,橙色線是我們要推導出的關系,黑色線是以知關系。

  

關系表如下

結點A與根關系 結點B與根關系 結點B與A的關系 B根節點和A根節點的關系
0 0 0 0
0 0 1 1
0 0 2 2
0 1 0 2
0 1 1 0
0 1 2 1
0 2 0 1
0 2 1 2
0 2 2 0

上面這個表並沒有列出所有情況,但是我們已經可以從表格中可以得到關系`relation[pre[b]->prea[a]] = (rela[a] - rela[b] + relation[b -> a]) % 3`合並關系的代碼如下

1 void Merge(int x, int y, int r){ // 合並兩個節點關系
2     int fx = Find(x);  // 查找 x,y的根節點
3     int fy = Find(y);
4 
5     if(fx != fy){  //如根節點不同進行合並
6         pre[fx] = fy;   //把x節點集合合並到y
7         rela[fx] = (rela[y] - rela[x] + r + 3) % 3; //計算x頭節點與y頭節點的關系
8     }
9 }
View Code

AC代碼

  1 #include <cstdio>
  2 #include <cstring>
  3 #include <cmath>
  4 #include <cstdlib>
  5 #include <ctime>
  6 #include <cctype>
  7 #include <cstring>
  8 #include <cmath>
  9 #include <iostream>
 10 #include <sstream>
 11 #include <string>
 12 #include <list>
 13 #include <vector>
 14 #include <set>
 15 #include <map>
 16 #include <queue>
 17 #include <stack>
 18 #include <algorithm>
 19 #include <functional>
 20 #define pr pair<int,LL>
 21 #define lowbit(x) (x&(-x))
 22 #define rep(i,a,n) for (int i=a;i<=n;i++)
 23 #define per(i,a,n) for (int i=a;i>=n;i--)
 24 #define mem(ar,num) memset(ar,num,sizeof(ar))
 25 #define debug(x) cout << #x << ": " << x << endl
 26 using namespace std;
 27 typedef long long LL;
 28 typedef unsigned long long ULL;
 29 const int    prime = 999983;
 30 const int    INF = 0x7FFFFFFF;
 31 const LL     INFF =0x7FFFFFFFFFFFFFFF;
 32 const double pi = acos(-1.0);
 33 const double inf = 1e18;
 34 const double eps = 1e-6;
 35 const LL     mod = 1e9 + 7;
 36 const int    maxn = 5e5 + 7;
 37 const int    maxm = 4e6 + 7;
 38 
 39 
 40 inline int read () {   //讀入優化
 41     int X = 0, w = 1; char ch = 0;
 42     while(ch < '-') { if(ch == '-') w = -1; ch = getchar(); }
 43     while(ch >= '0' && ch <= '9') X = (X << 3) + (X << 1) + ch - '0', ch = getchar();
 44     return X * w;
 45 }
 46 
 47 int pre[maxn],rela[maxn];
 48 int n, k, ans;
 49 
 50 void init() // 初始化
 51 {
 52     for(int i = 1; i <= n; i++){
 53         pre[i] = i; // 頭節點等於自己本身
 54         rela[i] = 0; // 自己和自己肯定是同類
 55     }
 56     ans = 0; //記錄假話數量
 57 }
 58 
 59 int Find(int x){   // 查找當前結點的根節點
 60     if(x == pre[x]) return x;
 61     else{       // 壓縮路徑
 62         int temp = pre[x];
 63         pre[x] = Find(pre[x]); // 遞歸尋找根節點,壓縮路徑節點
 64         rela[x] = (rela[x] + rela[temp]) % 3; // 壓縮路徑關系
 65     }
 66     return pre[x];
 67 }
 68 
 69 void Merge(int x, int y, int r){ // 合並兩個節點關系
 70     int fx = Find(x);  // 查找 x,y的根節點
 71     int fy = Find(y);
 72 
 73     if(fx != fy){  //如根節點不同進行合並
 74         pre[fx] = fy;   //把x節點集合合並到y
 75         rela[fx] = (rela[y] - rela[x] + r + 3) % 3; //計算x頭節點與y頭節點的關系
 76     }
 77 
 78 }
 79 
 80 bool solve(int x,int y,int r){ // 判斷真話假話
 81     int relation;
 82     if(x > n||y > n||(r == 1&&x == y)){ // 根據題意直接判斷的假話
 83             return false;
 84     }
 85     if(Find(x) == Find(y)){ // 如果兩個根節點相同
 86         relation = (rela[x] - rela[y] + 3) % 3; // 推出兩個根節點之間的關系
 87         return relation == r; // 判斷給出關系是否與已經存在的關系矛盾
 88     }
 89     else
 90         return true; //否則為真
 91 }
 92 /// 0 表示與根節點是同類
 93 /// 1 表示與根節點是捕食關系
 94 /// 2 表示與根節點是被捕食關系
 95 int main()
 96 {
 97     n = read();
 98     k = read();
 99     init();
100     int c, x, y;
101     while(k--){
102         c = read();
103         x = read();
104         y = read();
105         c --;
106         if(solve(x,y,c)){
107             Merge(x,y,c); //真話合並兩個節點關系
108         }else{
109             ans++; //假話答案自增
110         }
111     }
112     printf("%d\n",ans);
113     return 0;
114 }
View Code

我在剛學習帶權並查集時看的是這位大佬的博客,大家也可以進行參考:帶權並查集

第一次寫博客,以上是我的一些個人理解,如有錯誤麻煩各位大佬指正。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM