並查集—應用
有人說:並查集好寫,好用,就是沒什麼地方用。的確,並查集應用不是特別廣,但作為一種優質演算法,這裡還是要多說幾句。並查集的問題主要分成兩大類:帶權並查集,種類並查集。
一、帶權並查集
帶權並查集就是讓每個節點除了記錄自己父親以外,還記錄一些其它的東西(如:集合的大小),通過它記錄的資訊來解決題目。
例題1:(來源: caioj 1095)
1. M i j :合併指令,i和 j是指令涉及的戰艦編號。該指令是將i號戰艦所在的整個戰艦接至第 j號戰艦所在的戰艦佇列的尾部。
2. C i j :詢問指令,i和 j是指令涉及的戰艦編號。第 i 號戰艦與第 j 號戰艦當前是否在同一列中,如果在同一列中,那麼它們之間佈置有多少戰艦。
看到“合併”,很容易想到並查集,這題合併的戰艦要求保持有序,那對並查集的要求有點高了。
思路:我們可以對戰艦組進行編號,戰艦頭記為1,戰艦尾在艦隊中的編號特別記錄在tail中,由戰艦頭負責管理。在戰艦組中的每艘戰艦要記住該戰艦組的頭,它就像是艦隊的首領,同時,不能忘記自己在戰艦組中的位置。這樣,對於每次詢問,我們只要先看看這兩艘戰艦是否在同一戰艦組,是,則輸出它們的位置差。
程式碼:
#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int fa[30010],dis[30010],tail[30010];//dis記錄戰艦在艦隊中的位置 tail記錄該戰艦的尾戰艦在艦隊中的編號,也能反映艦隊的大小
char c[5];
int find_fa(int x)
{
if(fa[x]==x) return fa[x];
int f=find_fa(fa[x]);//先讓艦隊頭更新當前時局
dis[x]=dis[x]+dis[fa[x]]-1;//自己再更新
fa[x]=f;
return fa[x];
}
int main()
{
int T;
scanf("%d",&T);
for(int i=1;i<=30000;i++)
{
fa[i]=i;
dis[i]=1;
tail[i]=1;
}
while(T--)
{
int x,y;
scanf("%s%d%d",c,&x,&y);
if(c[0]=='M')
{
int fx=find_fa(x),fy=find_fa(y);
fa[fx]=fy;
dis[fx]=tail[fy]+1;//fx更新自己在艦隊中的位置
tail[fy]=tail[fy]+tail[fx];//fy更新擴充套件後的艦隊的資訊
}
else
{
int fx=find_fa(x),fy=find_fa(y);
if(fx!=fy)
{
printf("-1\n");
continue;
}
printf("%d\n",abs(dis[x]-dis[y])-1);
}
}
return 0;
}
二、種類並查集
種類並查集就是已經給出了兩兩關係判斷方式的並查集,要求你根據這種判斷方式來求出其中某兩個的關係。通常我們需要用一個re陣列,記錄下我與我父親的關係,此外還要求出關係的轉移式,使得每個集合內的關係和合並集合時的關係能夠準確無誤的展現。
例題2:(來源: caioj 1096)
動物王國中有三類動物A,B,C,這三類動物的食物鏈構成了有趣的環形。A吃B, B吃C,C吃A。
第一種說法是“1 X Y”,表示X和Y是同類。
第二種說法是“2 X Y”,表示X吃Y。
當一句話滿足下列三條之一時,這句話就是假話,否則就是真話。當前的話與前面的某些真的話衝突,就是假話;當前的話中X或Y比N大,就是假話;當前的話表示X吃X,就是假話。 輸出假話的總數。
如果用一個belong陣列簡單記錄下每種動物的屬性,在合併兩條食物鏈時,會顯得無從下手,既然又栽在了合併的問題上,為什麼不考慮並查集呢?
思路:用re陣列記錄下我與父親的關係:0:我與父親同類,或者我沒有父親;1:我吃父親;2:我被父親吃。
如圖(箭頭從吃指向被吃),我們來理一理關係的計演算法則。
D與A的關係應該為0,其實是(re[x]+re[fa[x]])%3的結果。由此,我們得到,一條食物鏈上的關係可以用加法解決。
A與C的關係應該為1(即A吃C),其實是(3-re[x])%3的結果。由此,我們得到,若將關係的發出者交換,關係為(3-re[x])%3。
B與C的關係應該為2(即B被C吃),根據結論2,我們可以把B與A的關係和C與A的關係,轉換為B與A的關係和A與C的關係,現在B和C就在一條順著的關係上了。再根據結論1,我們得到B與C的關係為(3-re[y])%3+re[x],整理得(re[x]-re[y]+3)%3。由此,我們求得了與祖先有直接關係的動物跨過祖先後,它們關係為(re[x]-re[y]+3)%3。
接下來,如果發現X與Y在一條食物鏈裡,我們讓X與Y直接與父親相連(路徑壓縮),那麼我們通過 (re[x]-re[y]+3)%3 可以找到X與Y的關係;特別的,當X或Y沒有父親時,也不影響通過該公式求出兩個動物間的關係。
程式碼:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n,k,ans=0;
int fa[50010],re[50010];//x與fa[x]的關係(0,1,2)
int find_fa(int x)
{
if(fa[x]==x) return fa[x];
int f=find_fa(fa[x]);
re[x]=(re[x]+re[fa[x]])%3;//關係公式
fa[x]=f;
return fa[x];
}
int main()
{
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++)
{
fa[i]=i;
re[i]=0;
}
while(k--)
{
int d,x,y;
scanf("%d%d%d",&d,&x,&y);
if(x>n||y>n) ans++;
else if(d==2&&x==y) ans++;
else
{
int fx=find_fa(x),fy=find_fa(y);
if(fx==fy)//在一條記錄好了的食物鏈中
{
if((re[x]-re[y]+3)%3!=d-1) ans++;//關係公式
}
else//以前沒有記錄過關係,此話為真,所以記錄下它們的關係
{
fa[fx]=fy;
if(d==1) re[fx]=(re[y]-re[x]+3)%3;//關係公式
else re[fx]=(1-re[x]+re[y]+3)%3;//關係公式
}
}
}
printf("%d",ans);
return 0;
}
總結:對於種類並查集,先制定好種類編號,用加法的形式檢驗。合格後,繼續尋找其它的關係公式。最後,應用到並查集模版中。
通過以上兩題,有沒有發現,對於複雜些的並查集,它的find_fa函式總是有以下格式:
int find_fa(int x)
{
if(fa[x]==x) return fa[x];
int f=find_fa(fa[x]);
//隨意幹些奇奇怪怪的事
fa[x]=f;
return fa[x];
}
要記得先讓舊父親尋找新父親,接著處理自己的事,最後才更新自己的最新父親。因為要使舊父親的情況與最新情況同步,自己才能把與新父親的關係處理好,進而拋棄舊父親,認最新的父親。
推薦:《並查集—入門》http://blog.csdn.net/a_bright_ch/article/details/77161640
相關文章
- 並查集應用並查集
- 並查集深度應用並查集
- 並查集的應用2並查集
- 並查集(二)並查集的演算法應用案例上並查集演算法
- 並查集的簡單應用並查集
- 並查集擴充套件應用並查集套件
- 並查集經典應用場景並查集
- 【學習筆記】並查集應用筆記並查集
- 【帶權並查集】理論和應用並查集
- 並查集在實際問題中的應用並查集
- 並查集到帶權並查集並查集
- 查並集
- 【並查集】【帶偏移的並查集】食物鏈並查集
- 並查集(一)並查集的幾種實現並查集
- 並查集(小白)並查集
- [leetcode] 並查集(Ⅱ)LeetCode並查集
- [leetcode] 並查集(Ⅲ)LeetCode並查集
- [leetcode] 並查集(Ⅰ)LeetCode並查集
- 3.1並查集並查集
- 並查集演算法Union-Find的思想、實現以及應用並查集演算法
- 寫模板, 並查集。並查集
- 並查集的使用並查集
- 並查集跳躍並查集
- 各種並查集並查集
- 淺談並查集並查集
- 食物鏈(並查集)並查集
- 並查集(Union Find)並查集
- The Door Problem 並查集並查集
- 並查集練習並查集
- 並查集題目合集並查集
- 並查集java實現並查集Java
- 【轉】種類並查集並查集
- The Suspects-並查集(4)並查集
- 並查集擴充套件並查集套件
- (Day3)並查集並查集
- 並查集演算法並查集演算法
- 並查集-Java實現並查集Java
- 簡單易懂的並查集演算法以及並查集實戰演練並查集演算法
- 專題五 並查集【Kuangbin】並查集