1. 程式人生 > >A Bug's Life(並查集)

A Bug's Life(並查集)

Problem Description Background 
Professor Hopper is researching the sexual behavior of a rare species of bugs. He assumes that they feature two different genders and that they only interact with bugs of the opposite gender. In his experiment, individual bugs and their interactions were easy to identify, because numbers were printed on their backs. 

Problem 

Given a list of bug interactions, decide whether the experiment supports his assumption of two genders with no homosexual bugs or if it contains some bug interactions that falsify it.
Input The first line of the input contains the number of scenarios. Each scenario starts with one line giving the number of bugs (at least one, and up to 2000) and the number of interactions (up to 1000000) separated by a single space. In the following lines, each interaction is given in the form of two distinct bug numbers separated by a single space. Bugs are numbered consecutively starting from one.
Output
            The output for every scenario is a line containing "Scenario #i:", where i is the number of the scenario starting at 1, followed by one line saying either "No suspicious bugs found!" if the experiment is consistent with his assumption about the bugs' sexual behavior, or "Suspicious bugs found!" if Professor Hopper's assumption is definitely wrong.
Sample Input
2
3 3
1 2
2 3
1 3
4 2
1 2
3 4
Sample Output
Scenario #1:
Suspicious bugs found!

Scenario #2:
No suspicious bugs found!

HintHuge input,scanf is recommended.
Source TUD Programming Contest 2005, Darmstadt, Germany
Recommend linle

這是第一次接觸並查集,看了看課本上的有關知識,算是有初步的瞭解,還是要多做題!!加油!!!!!!

1、什麼叫並查集
  並查集(union-find set)是一種用於分離集合操作的抽象資料型別。它所處理的是“集合”之間的關係,即動態地維護和處理集合元素之間複雜的關係,當給出兩個元素的一個無序對(a,b)時,需要快速“合併”a和b分別所在的集合,這其間需要反覆“查詢”某元素所在的集合。“並”、“查”和“集”三字由此而來。在這種資料型別中,n個不同的元素被分為若干組。每組是一個集合,這種集合叫做分離集合(disjoint set)。並查集支援查詢一個元素所屬的集合以及兩個元素各自所屬的集合的合併。
  例如,有這樣的問題:初始時n個元素分屬不同的n個集合,通過不斷的給出元素間的聯絡,要求實時的統計元素間的關係(是否存在直接或間接的聯絡)。這時就有了並查集的用武之地了。元素間是否有聯絡,只要判斷兩個元素是否屬於同一個集合;而給出元素間的聯絡,建立這種聯絡,則只需合併兩個元素各自所屬的集合。這些操作都是並查集所提供的。
   並查集本身不具有結構,必須藉助一定的資料結構以得到支援和實現。資料結構的選擇是一個重要的環節,選擇不同的資料結構可能會在查詢和合並的操作效率上有很大的差別,但操作實現都比較簡單高效。並查集的資料結構實現方法很多,陣列實現、連結串列實現和樹實現。一般用的比較多的是陣列實現。

2、並查集支援的操作

    並查集的資料結構記錄了一組分離的動態集合S={S1,S2,…,Sk}。每個集合通過一個代表加以識別,代表即該元素中的某個元素,哪一個成員被選做代表是無所謂的,重要的是:如果求某一動態集合的代表兩次,且在兩次請求間不修改集合,則兩次得到的答案應該是相同的。
    動態集合中的每一元素是由一個物件來表示的,設x表示一個物件,並查集的實現需要支援如下操作:
  MAKE(x):建立一個新的集合,其僅有的成員(同時就是代表)是x。由於各集合是分離的,要求x沒有在其它集合中出現過。
  UNIONN(x,y):將包含x和y的動態集合(例如Sx和Sy)合併為一個新的集合,假定在此操作前這兩個集合是分離的。結果的集合代表是Sx∪Sy的某個成員。一般來說,在不同的實現中通常都以Sx或者Sy的代表作為新集合的代表。此後,由新的集合S代替了原來的Sx和Sy。
    FIND(x):返回一個指向包含x的集合的代表。下面有一種優化的方法:

並查集的路徑壓縮
  此種做法就是將元素的父親結點指來指去地指,當這棵樹是鏈的時候,可見判斷兩個元素是否屬於同一集合需要O(n)的時間,於是路徑壓縮產生了作用。
  路徑壓縮實際上是在找完根結點之後,在遞歸回來的時候順便把路徑上元素的父親指標都指向根結點。

-------------------------------------------------------------------------------------------------------------------------------------------以下轉自:http://www.cnblogs.com/dongsheng/archive/2012/08/08/2627917.html

/* 功能Function Description:     POJ-2492 並查集應用的擴充套件
   開發環境Environment:          DEV C++ 4.9.9.1
   技術特點Technique:
   版本Version:
   作者Author:                   可笑痴狂
   日期Date:                      20120808
   備註Notes:
關於並查集,注意兩個概念:按秩合併、路徑壓縮。
1、按秩合併
由於並查集一般是用比較高效的樹形結構來表示的,按秩合併的目的就是防止產生退化的樹(也就是類似連結串列的樹),
用一個數組記錄各個元素的高度(也有的記錄各個元素的孩子的數目,具體看哪種能給解題帶來方便),
然後在合併的時候把高度小的嫁接到高度大的上面,從而防止產生退化的樹。
2、路徑壓縮
而另一個數組記錄各個元素的祖先,這樣就防止一步步地遞迴查詢父親從而損失的時間。因為並查集只要搞清楚各個元素所在的集合,
而區分不同的集合我們用的是代表元素(也就是樹根),所以對於每個元素我們只需儲存其祖先,從而區分不同的集合。
而我們這道題並沒有使用純正的並查集演算法,而是對其進行了擴充套件,
我們並沒有使用“1、按秩合併”(當然你可以用,那樣就需要再開一個數組)
我們從“1、按秩合併”得到啟示,儲存“秩”的陣列儲存的是    元素相對於父節點的關係  ,我們豈不可以利用這種關係
(即相對於父節點的不同秩值來區分不同的集合),從而可以把兩個集合合併成一個集合。
(注:此程式碼 relation=0 代表 和父節點同一性別)
*/

#include<stdio.h>
int father[2005];
int relation[2005];


int find_father(int i)
{
    int t;
    if(father[i]==i)
        return i;


    //計算相對於新的父節點(即根)的秩,relation[t]是老的父節點相對於新的父節點(即根)的秩,relation[i]是i元素相對於老的父節點的秩,
    //類似於物理裡的相對運動,得到的r[i]就是相對於新的父節點(即根)的秩。而且這個遞迴呼叫不會超過兩層
    t=father[i];    
    father[i]=find_father(father[i]);
    relation[i]=(relation[i]+relation[t]+1)%2;   //注意遞迴中把這棵樹relation中的的值都更新一遍,這句的順序 不能 和上一句 調換位置
    // relation[a]的改變是伴隨著father[a]的改變而更新的(有father改變就有relation改變),要是father改變了,而relation未改變,此時的relation就記錄了一個錯誤的值,
    //father未改變(即使實際的father已不是現在的值,但只要father未改變,relation的值就是“正確”的,認識到這點很重要。)
    return father[i];
}


void merge(int a,int b)
{
    int x,y;
    x=find_father(a);
    y=find_father(b);
    father[x]=y;
    relation[x]=(relation[b]-relation[a])%2;//relation[a]+relation[x]與relation[b]相對於新的父節點必須相差1個等級,因為他們不是gay
}                                            //x下邊的節點不用改,因為查詢的時候會自動更新


int main()
{
    int T,m,n,i,j,a,b,flag;
    scanf("%d",&T);
    for(i=1;i<=T;++i)
    {
        flag=0;
        scanf("%d%d",&n,&m);
        for(j=1;j<=n;++j)       //初始化
        {
            father[j]=j;
            relation[j]=1;
        }
        for(j=1;j<=m;++j)
        {
            scanf("%d%d",&a,&b);
            if(find_father(a)==find_father(b))
            {
            //    if(relation[a]!=(relation[b]+1)%2)
                if(relation[a]==relation[b])            //說明是同性
                    flag=1;
            }
            else
                merge(a,b);
        }
        if(flag)
            printf("Scenario #%d:\nSuspicious bugs found!\n\n",i);
        else
            printf("Scenario #%d:\nNo suspicious bugs found!\n\n",i);
    }
    return 0;
}