並查集:學習總結
$ ??????????????? ? $學習總結:並查集
蒟蒻的第一篇博客,如有bug,請大佬提出,勿噴。
並查集:
並查集雖說是集合,不過個人覺得類似樹形結構,像森林,剛開始每一個節點是一個森林,不斷把森林合並,形成樹。
? 是一組不相交的集合。即集合之間沒有公共元素,它們的交集是空集。
? 因此沒有“求集合的交集”和“求集合的差集” 操作。
? 至於“求集合的並集”運算,則只需簡單地將兩個集合的元素合並在一起就行了,不用考慮剔除重復元素的問題。
上模板:
【 P3367 【模板】並查集】
題目描述
如題,現在有一個並查集,你需要完成合並和查詢操作。
輸入輸出格式
輸入格式:
第一行包含兩個整數N、M,表示共有N個元素和M個操作。
接下來M行,每行包含三個整數Zi、Xi、Yi
當Zi=1時,將Xi與Yi所在的集合合並
當Zi=2時,輸出Xi與Yi是否在同一集合內,是的話輸出Y;否則話輸出N
輸出格式:
如上,對於每一個Zi=2的操作,都有一行輸出,每行包含一個大寫字母,為Y或者N
代碼:
#include<iostream> using namespace std; int father[100000+10]; void init(){ //初始化 for(int i=0;i<10000+10;i++) father[i]=i; //爸爸就是自己 } int find(int x){ //找祖先 if(father[x]==x) return x; //如果爸爸是自己的話,那麽他就是這整個集合的root,及祖先。 else return father[x]=find(father[x]); //否則繼續向上找 } void findans(int x,int y){ //判兩數是否是否在同一集合(並查集) int i=find(x); //找祖先 int j=find(y); //同上 if(i==j) cout<<'Y'<<endl; //如果祖先相同,及在同一並查集 else cout<<'N'<<endl; } void union_(int x,int y){ //合並 int i=find(x); int j=find(y); if(i!=j) father[i]=j; //將一個數的父親指向另一個數 } int main(){ int n,m,p; cin>>n>>m; init(); for(int i=1;i<=m;i++){ int e,a,b; cin>>e>>a>>b; if(e==1) //操作:合並 union_(a,b); else //操作:詢問 findans(a,b); } return 0; }
帶秩並查集:
什麽是秩:秩就是並查集的節點數。
經典題:【打擊犯罪】
某個地區有n(n≤1000)個犯罪團夥,當地警方按照他們的危險程度由高到低給他們編號為1-n,他們有些團夥之間有直接聯系,但是任意兩個團夥都可以通過直接或間接的方式聯系,這樣這裏就形成了一個龐大的犯罪集團,犯罪集團的危險程度由集團內的犯罪團夥數量唯一確定,而與單個犯罪團夥的危險程度無關(該犯罪集團的危險程度為n)。現在當地警方希望花盡量少的時間(即打擊掉盡量少的團夥),使得龐大的犯罪集團分離成若幹個較小的集團,並且他們中最大的一個的危險程度不超過n/2。為達到最好的效果,他們將按順序打擊掉編號1到k的犯罪團夥,請編程求出k的最小值。
【輸入】
第一行一個正整數n。接下來的n行每行有若幹個正整數,第一個整數表示該行除第一個外還有多少個整數,若第i行存在正整數k,表示i,k兩個團夥可以直接聯系。
【輸出】
一個正整數,為k的最小值。
【輸入樣例】
7
2 2 5
3 1 3 4
2 2 4
2 2 3
3 1 6 7
2 5 7
2 5 6
【輸出樣例】
1
【思路】:審題,題目要求,按順序打擊1~k的罪犯團夥,也就是說,要打k號犯罪團夥,必須先打掉1~k-1號犯罪團夥。所以,我們可以從後往前建並查集,一旦發現並查集的秩超過了題目要求(n/2)那麽直接輸出結果(當前操作的編號)。
【代碼】
#include<iostream>
#include<cmath>
#include<cstdlib>
#include<cstdio>
using namespace std;
int father[100000+10],num[100000+10],dis[100000+10],ans,k,o,e[1000001],a[1001][1001];
void init() //初始化
{
for(int i=0;i<50000+10;i++){father[i]=i;num[i]=1;}
}
int find(int x) //找祖先
{
if(x!=father[x])
{
father[x]=find(father[x]);
}
return father[x];
}
void merge(int x,int y)
{
int a1=find(x),a2=find(y);
if(a1!=a2)
{
father[a1]=a2;
num[a2]+=num[a1]; //秩累加
if(num[a2]>k){cout<<o<<endl;exit(0);} //判斷是否滿足題意
//if(num[a2]>=k){cout<<o<<endl;exit(0);}
// num[a1]=num[a2];
}
}
int main()
{
int n,m,p;
cin>>m;
k=m/2;
init();
for(int i=1;i<=m;i++)
{
cin>>e[i];
for(int j=1;j<=e[i];j++)
{
cin>>a[i][j];
}
}
for(int i=m;i>=1;i--)
{
o=i;
for(int j=1;j<=e[i];j++)
{
if(i<a[i][j]) //如果(i>a[i][j]) a[i][j]已經被打掉了,所以沒用
merge(i,a[i][j]); //建立並查集
}
}
return 0;
}
帶權並查集&&n倍空間
什麽是權:權就是權值……
經典題:【食物鏈】&&【銀河英雄傳說】
P1196 [NOI2002]銀河英雄傳說
題目描述
公元五八○一年,地球居民遷移至金牛座α第二行星,在那裏發表銀河聯邦創立宣言,同年改元為宇宙歷元年,並開始向銀河系深處拓展。
宇宙歷七九九年,銀河系的兩大軍事集團在巴米利恩星域爆發戰爭。泰山壓頂集團派宇宙艦隊司令萊因哈特率領十萬余艘戰艦出征,氣吞山河集團點名將楊威利組織麾下三萬艘戰艦迎敵。
楊威利擅長排兵布陣,巧妙運用各種戰術屢次以少勝多,難免恣生驕氣。在這次決戰中,他將巴米利恩星域戰場劃分成30000列,每列依次編號為1, 2, …, 30000。之後,他把自己的戰艦也依次編號為1, 2, …, 30000,讓第i號戰艦處於第i列(i = 1, 2, …, 30000),形成“一字長蛇陣”,誘敵深入。這是初始陣形。當進犯之敵到達時,楊威利會多次發布合並指令,將大部分戰艦集中在某幾列上,實施密集攻擊。合並指令為M i j,含義為讓第i號戰艦所在的整個戰艦隊列,作為一個整體(頭在前尾在後)接至第j號戰艦所在的戰艦隊列的尾部。顯然戰艦隊列是由處於同一列的一個或多個戰艦組成的。合並指令的執行結果會使隊列增大。
然而,老謀深算的萊因哈特早已在戰略上取得了主動。在交戰中,他可以通過龐大的情報網絡隨時監聽楊威利的艦隊調動指令。
在楊威利發布指令調動艦隊的同時,萊因哈特為了及時了解當前楊威利的戰艦分布情況,也會發出一些詢問指令:C i j。該指令意思是,詢問電腦,楊威利的第i號戰艦與第j號戰艦當前是否在同一列中,如果在同一列中,那麽它們之間布置有多少戰艦。
作為一個資深的高級程序設計員,你被要求編寫程序分析楊威利的指令,以及回答萊因哈特的詢問。
最終的決戰已經展開,銀河的歷史又翻過了一頁……
【樣例輸入】
4
M 2 3
C 1 2
M 2 4
C 4 2
【樣例輸出】
-1
1
說明
【樣例說明】
戰艦位置圖:表格中阿拉伯數字表示戰艦編號
【思路】
在建並查集的過程中儲存兩戰艦的距離,找答案的時候加以處理。
【代碼】
#include<iostream>
#include<cmath>
#include<cstdio>
using namespace std;
int father[100000+10],num[100000+10],dis[100000+10],ans;
void init() //初始化
{
for(int i=0;i<50000+10;i++){father[i]=i;num[i]=1;}
}
int find(int x) //找祖先
{
if(x!=father[x])
{
int k=father[x];
father[x]=find(father[x]);
dis[x]+=dis[k]; //累加兩戰艦的距離
num[x]=num[father[x]]; //加權
}
return father[x];
}
int findans(int a,int b) //找答案
{
int r1=find(a),r2=find(b);
if(r1!=r2)
{
return -1;
}
else{
return abs(dis[a]-dis[b])-1;
}
}
void merge(int x,int y) //模板
{
int a1=find(x),a2=find(y);
if(a1!=a2)
{
father[a1]=a2;
dis[a1]=dis[a2]+num[a2];
num[a2]+=num[a1]; //權
num[a1]=num[a2];
}
}
int main(){
int n,m,p;
cin>>m;
init();
for(int i=1;i<=m;i++)
{
int a,b;
char e;
cin>>e>>a>>b;
if(e=='M')
merge(a,b); //建並查集
else
{
ans=findans(a,b);
printf("%d\n",ans);
}
}
return 0;
}
n倍空間並查集
【團夥】此題是雙倍空間,【食物鏈】是三倍空間,當然【食物鏈】也可以用帶權並查集來做。
【食物鏈】
描述
動物王國中有三類動物A,B,C,這三類動物的食物鏈構成了有趣的環形。A吃B, B吃C,C吃A。
現有N個動物,以1-N編號。每個動物都是A,B,C中的一種,但是我們並不知道它到底是哪一種。
有人用兩種說法對這N個動物所構成的食物鏈關系進行描述:
第一種說法是"1 X Y",表示X和Y是同類。
第二種說法是"2 X Y",表示X吃Y。
此人對N個動物,用上述兩種說法,一句接一句地說出K句話,這K句話有的是真的,有的是假的。當一句話滿足下列三條之一時,這句話就是假話,否則就是真話。
1) 當前的話與前面的某些真的話沖突,就是假話;
2) 當前的話中X或Y比N大,就是假話;
3) 當前的話表示X吃X,就是假話。
你的任務是根據給定的N(1 <= N <= 50,000)和K句話(0 <= K <= 100,000),輸出假話的總數。
輸入
第一行是兩個整數N和K,以一個空格分隔。
以下K行每行是三個正整數 D,X,Y,兩數之間用一個空格隔開,其中D表示說法的種類。
若D=1,則表示X和Y是同類。
若D=2,則表示X吃Y。
輸出
只有一個整數,表示假話的數目。
樣例輸入
100 7
1 101 1
2 1 2
2 2 3
2 3 3
1 1 3
2 3 1
1 5 5
樣例輸出
3
【思路】
開三倍空間,指的是把數組開三倍空間,分別存同類,獵物,天敵。然後把每一次輸入的數據進行判斷,如果發現不符題意,那麽就是謊話,否則把所有有關聯的同類連接在一起。
【代碼】
#include<iostream>
#include<string.h>
#include<cstdio>
#include<cstdlib>
using namespace std;
long long father[150000+10],t[1000001],ans,o,x,y,k,vis[100001],a,b,c;
void init(int n){ //三倍空間初始化
for(int i=1;i<=n;i++)
{
father[i]=i;
father[i+n]=i+n;
father[i+n+n]=i+n+n;
}
}
int find(int x) //找祖先
{
if(father[x]==x)
{
return x;
}
else
{return father[x]=find(father[x]);}
}
void union_(int x,int y){ //模板
int i=find(x);
int j=find(y);
if(i!=j){father[i]=j;}
}
int main()
{
int n,m,p;
cin>>n>>m;
init(n);
for(int i=1;i<=m;i++)
{
cin>>c>>a>>b;
if(a>n||b>n) //判斷符不符題意
{
ans++;
continue;
}
if(c==1) //同類情況
{
if(find(a+n)==find(b)||find(a+n+n)==find(b)) //判斷符不符題意
ans++;
else
{
union_(a,b); //同類處理
union_(a+n,b+n);
union_(a+n+n,b+n+n);
}
}
else
{
if(a==b) //判斷符不符題意
{
ans++;
continue;
}
if(find(a)==find(b)||find(a+n+n)==find(b))
ans++;
else
{
union_(a,b+n+n); //非同類處理
union_(b,a+n);
union_(a+n+n,b+n);
}
}
}
cout<<ans<<endl; //輸出
return 0;
}
總結:
1、並查集要註意關系,不能漏建,不能重建,不然會WA。
2、Find(),merge()的模板要記住,一般進行修改都會在這兩個函數裏改。
3、合並的時候有時要註意方向,不要搞反了。
隨便給點題:
UVA1316 Supermarket
UVA615 Is It A Tree?
UVA1197 The Suspects
P1195 口袋的天空
P1525 關押罪犯
POJ 1988 Parity game
(題解鏈接:大佬博客)
$ ??????????????? ? $ 謝謝觀賞,給個贊唄qwq
並查集:學習總結