並查集,在一些有N個元素的集合應用問題中,咱們一般是在開始時讓每一個元素構成一個單元素的集合,而後按必定順序將屬於同一組的元素所在的集合合併,其間要反覆查找一個元素在哪一個集合中。這一類問題近幾年來反覆出如今信息學的國際國內賽題中,其特色是看似並不複雜,但數據量極大,若用正常的數據結構來描述的話,每每在空間上過大,計算機沒法承受;即便在空間上勉強經過,運行的時間複雜度也極高,根本就不可能在比賽規定的運行時間(1~3秒)內計算出試題須要的結果,只能用並查集來描述。node
並查集是一種樹型的數據結構,用於處理一些不相交集合(Disjoint Sets)的合併及查詢問題。經常在使用中以森林來表示。(轉自百度百科)ios
衆所周知,每一個學校都有些奇怪的組織,有些是學校的,還有些''地下黨''(咳咳,懂得),而後每一個組織又分爲了幾個小組........當每一年開學的那一天。總有些新面孔會來到這些組織,可是顯然暫時不會。,這就意味着他們不屬於任何一個組織c++
for(int i=1;i<=n;i++) f[i]=i;
幾個月後,這些新同窗陸陸續續的加入了組織,有一天,學校讓全部的組織集合,這下好了,由於是新同窗,因此他們只記得本身的上級,因此只能去找上級,好巧不巧,上級也只認識本身的上級(????)最後一大堆人浩浩蕩蕩地找到了組織長,愉快地集合了起來
數組
int get(int x) { if(f[x]==x)return x;//找到根節點 return get(f[x]);//否則就繼續向上找 }
然而這些人的智商也不算低,記住了本身的組織長,下次集合直接找本身的組織長就能夠了,不用麻煩的走一趟了。數據結構
int get(int x) { if(f[x]==x)return x;//找到根節點 return f[x]=get(f[x]);//否則就繼續向上找,而後記錄 }
而後,學校嫌組織過多,要合併一些組織了,而後把XX部和OO部合併在了一塊兒,可是一個部得有一個領頭人啊,因此,咱們決定讓XX部的部長當了新的XO部的部長。這樣OO部部長的上級就是XX部部長了(OO部部長:爲何不是我)spa
void merge(int x,int y) { int tx=get(x); int ty=get(y); if(tx!=ty)//不在同一集合就合併(其實也能夠不要判斷,反正在同一集合合併了也不會變) fa[tx]=ty; return; }
P3367 【模板】並查集.net
嗯,這就是一道模板題3d
1 #include<bits/stdc++.h> 2 using namespace std; 3 int n,m; 4 int fa[10001]; 5 int get(int x)//找根結點 6 { 7 if(fa[x]==x)return x; 8 else return fa[x]=get(fa[x]);//路徑壓縮 9 } 10 void merge(int x,int y)//合併 11 { 12 int tx=get(x),ty=get(y); 13 if(tx!=ty) 14 fa[tx]=ty; 15 return; 16 } 17 int main() 18 { 19 scanf("%d%d",&n,&m); 20 for(int i=1;i<=n;i++) 21 fa[i]=i;//初始化 22 while(m--) 23 { 24 int z,x,y; 25 scanf("%d%d%d",&z,&x,&y); 26 if(z==1) 27 merge(x,y); 28 else 29 { 30 if(get(x)==get(y))cout<<"Y"<<endl; 31 else cout<<"N"<<endl; 32 } 33 } 34 return 0; 35 }
嗯,和普通的並查集沒什麼區別code
普通的並查集表明着集合與集合之間的關係,可是帶權並查集還維護了點與點之間的關係(畢竟帶權嘛)。可是上例題前,咱們先來康康怎麼來帶權並查集的路徑壓縮。blog
首先,這裏有一個未壓縮的並查集,假設查找E的根結點,咱們須要E到D的距離加上D到A的距離,可是D到A的距離,又須要D到B的距離加上B到A的距離.....
sum表示這個點到根結點的權值
1 int get(int x) 2 { 3 if(root[x]==x)return x; 4 int fa=get(root[x]); 5 sum[x]+=sum[root[x]]; 6 return root[x]=fa; 7 }
注意第四行,必定要寫在壓縮權值的前面,以圖爲例,若是寫在後面,那麼E到A的距離會變成ED加上DB,就是E到B的距離了,然而這不是咱們想要的結果,因此先找到根,而後從根結點慢慢回來,路上再更新權值,這樣纔是有效的。而不是從某個結點向根結點更新。
首先你有兩個關係
而後題目又給了你x到y的權值,如今有了這一條紅線你就要合併這兩個並查集了
咱們想把tx併到ty去,可是就必須得知道tx到ty的權值
咱們不難知道,x到ty的兩條線路的權值都得相同,因此只須要用v2+s-v1便可
root[tx]=ty; sum[tx]=s+sum[y]-sum[x];
嗯,看看這個題,大概就是有一串你不知道的數,每次給你一個區間的和,看是否與前面已知的重複。例如已知[2,9]=10,[2,5]=8,又給你[6,9]=3,那麼顯然不對,由於咱們從前面可知[3,9]應該等於2(本身算的出來吧)。
可是還有一個注意的地方,咱們用sum數組表明一個點到根結點的全部權值,可是初始化的時候例如sum[1]=0(此時1自己是根結點),這個表明的是[1,1]等於0,也就是a[1]到a[1]等於0.可是這樣的話就表示你已經確切的知道了這個值,然而咱們不知道這個值,因此不妨把閉區間的某一段變成開區間,例如[1,1]改爲(0,1],這樣sum[1]就表明(1,1],也不會衝突了。
1 #include<bits/stdc++.h> 2 using namespace std; 3 int n,m,ans; 4 int root[200005],sum[200005]; 5 int get(int x)//查找 6 { 7 if(root[x]==x)return x; 8 int fa=get(root[x]); 9 sum[x]+=sum[root[x]]; 10 return root[x]=fa; 11 } 12 int main() 13 { 14 while(~scanf("%d%d",&n,&m)) 15 { 16 ans=0; 17 for(int i=0;i<=n;i++)//初始化,必定要從0開始 18 { 19 root[i]=i; 20 sum[i]=0; 21 } 22 while(m--) 23 { 24 int x,y,s; 25 scanf("%d%d%d",&x,&y,&s); 26 x--; 27 int tx=get(x); 28 int ty=get(y); 29 if(tx==ty)//若是是同一個並查集,就看是否合法便可 30 { 31 if(sum[x]-sum[y]!=s) 32 ans++; 33 } 34 else//不然合併一下 35 { 36 root[tx]=ty; 37 sum[tx]=s+sum[y]-sum[x]; 38 } 39 } 40 cout<<ans; 41 } 42 return 0; 43 }
種類並查集,就像他的名字同樣,有幾個種類,就有幾個區間,對應的就要開幾倍數組。所謂區間,就像下面的圖同樣:
咱們如今假設有兩個種類,而後每個數都有多是x種類或者y種類,隨之對應的,若是一個數m是x種類,那麼它就是m,不然它是m+n
.再來看看例題,它有三個種類,因此咱們要開三倍數組,而後首先每一個數的根結點都是本身,咱們現有三個種類:A,B,C,而後獲得了a吃b,a有三種狀況,當a爲A種類時,b就爲B種類,當a爲B種類時,b就爲C種類.......而後三個並查集分別維護,一旦發生衝突,就ans++便可
1 #include<iostream> 2 using namespace std; 3 int root[500005]; 4 int n,m,ans; 5 int get(int x)//查找 6 { 7 if(root[x]==x)return x; 8 return root[x]=get(root[x]); 9 } 10 void merge(int x,int y)//合併 11 { 12 int tx=get(x); 13 int ty=get(y); 14 root[tx]=ty; 15 return; 16 } 17 int main() 18 { 19 scanf("%d%d",&n,&m); 20 for(int i=1;i<=3*n;i++)//初始化,三倍數組 21 root[i]=i; 22 while(m--) 23 { 24 int x,y,z; 25 scanf("%d%d%d",&z,&x,&y); 26 if(x>n||y>n||z==2&&x==y)ans++;//不合法 27 else if(z==1)//同一種類 28 { 29 if(get(x+n)==get(y)||get(x+2*n)==get(y))//若是他們已經有了關係而且不是同一種類,ans++ 30 { 31 ans++; 32 continue; 33 } 34 merge(x,y);//都是A種 35 merge(x+n,y+n);//都是B種 36 merge(x+2*n,y+2*n);//都是C種 37 } 38 else//x吃y 39 { 40 if(get(x)==get(y)||get(x+n)==get(y))//若是他們有了關係而且是同種類或者x被y吃,ans++ 41 { 42 ans++; 43 continue; 44 } 45 merge(x,y+n);//x是A種,吃爲B種的y 46 merge(x+n,y+2*n);//x是B種,吃爲C種的y 47 merge(x+2*n,y);//x是C種,吃爲A種的y 48 } 49 } 50 cout<<ans; 51 }
而後這道題也能夠用權值並查集來作,咱們假設同根結點比,0是同類,1被根結點吃,2吃根結點,而後初始化是每一個點都是本身的同類,和權值並查集的合併同樣.可是由於這裏的關係只有三種,因此當咱們維護權值的時候有一個取模三的操做。如圖,tx與ty的關係就是(1+0+3-2)%3=2.(然而我好像沒有這樣寫)
1 #include<iostream> 2 using namespace std; 3 int n,m,ans; 4 struct node{ 5 int root; 6 int val;//0同類,1被吃,2吃根 7 }a[50005]; 8 int get(int x) 9 { 10 if(a[x].root==x)return x; 11 int y=a[x].root,z=get(y); 12 a[x].val=(a[x].val+a[y].val)%3; 13 return a[x].root=z; 14 } 15 int merge(int z,int x,int y) 16 { 17 int tx=get(x),ty=get(y); 18 if(tx==ty)//在同一集合 19 { 20 if((a[y].val+(3-a[x].val))%3!=z-1)//判斷是否衝突 21 return 1; 22 } 23 else 24 { 25 a[ty].root=tx; 26 a[ty].val=((z-1)+(3-a[y].val)+a[x].val)%3;//算關係 27 } 28 return 0; 29 } 30 int main() 31 { 32 scanf("%d%d",&n,&m); 33 for(int i=1;i<=n;i++) 34 { 35 a[i].root=i; 36 a[i].val=0; 37 } 38 while(m--) 39 { 40 int x,y,z; 41 scanf("%d%d%d",&z,&x,&y); 42 if(x>n||y>n)ans++; 43 else if(z==2&&x==y)ans++; 44 else ans+=merge(z,x,y);//合併 45 } 46 cout<<ans; 47 }
其實種類並查集就是權值並查集一個特殊的存在,總而言之都是並查集就對了(滑稽)