【經常使用數據結構——並查集(又在亂牽線了)】

並查集

簡介

  並查集,在一些有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];

HDU—3008

嗯,看看這個題,大概就是有一串你不知道的數,每次給你一個區間的和,看是否與前面已知的重複。例如已知[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 }

 種類並查集

POJ-1182

種類並查集,就像他的名字同樣,有幾個種類,就有幾個區間,對應的就要開幾倍數組。所謂區間,就像下面的圖同樣:

咱們如今假設有兩個種類,而後每個數都有多是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 }

其實種類並查集就是權值並查集一個特殊的存在,總而言之都是並查集就對了(滑稽)

相關文章
相關標籤/搜索