1. 程式人生 > >並查集基本操作及擴充套件

並查集基本操作及擴充套件

一.基本定義

1.並查集是一種維護集合的資料結構,其名字中的並 查 集分別取自union(合併),find(查詢),set(集合)。支援下面兩個操作

合併:合併兩個集合。

查詢:判斷兩個元素是否在一個集合中。

2.並查集的實現。int father[]。用一個father[]陣列儲存集合。其中father[2]=3意為元素2的父親結點為元素3。多個元素屬於同一個集合依據為具有相同的根節點。如果father[i]=i則說明元素i是該集合的根節點。對同一個集合來說,只能存在一個根節點,並將此作為該集合的標記。

二.基本操作

1.初始化

for(int i=1;i<=n;++i){
father[i]=i;
}

2.查詢(對給定結點找到其根節點的過程)

遞推寫法:

int find_far(int x){
int a=x;
while(x!=father[x]){
    x=father[x];
   }
//路徑壓縮,可寫可不寫
while(a!=father[a]){
    int z=a;
    a=father[a];
    father[z]=x;
 }
  return x;
}

遞迴寫法:

int find_far(int x){
    if(x==father[x])return x;
    else return find_far(father[x]);
}

3.合併(在合併過程中,只對兩個不同集合進行合併,同一個集合不會進行操作,這保證了同一個集合中一定不會產生環,也就是並查集產生的每個集合都是一棵樹。):

void Union(int a,int b){
    int fa=find_far(a);
    int fb=find_far(b);
    if(fa!=fb){
        father[fa]=fb;
    }
}

三.並查集的應用

1.刪除並查集中的某個元素 ,使該元素自成一個集合,並時集合中與該元素有關係的元素保持不變。

解決方案:初始時,對每個結點,其父親結點不再是本身,而設為虛擬父親,在從集合中刪除結點時,則為該結點重新申請一個父親。假如初始時一共有三個結點,初始時令father[1]=4,father[2]=5,father[3]=6。進行合併操作Union(2,1),Union(3,1),此時father[1]=4,father[2]=4,father[3]=4.若要從該集合中刪除結點1,則令father[1]=7,此時結點2,3是一個集合,而結點1成為一個新的集合

Problem Description

Recognizing junk mails is a tough task. The method used here consists of two steps: 1) Extract the common characteristics from the incoming email. 2) Use a filter matching the set of common characteristics extracted to determine whether the email is a spam. We want to extract the set of common characteristics from the N sample junk emails available at the moment, and thus having a handy data-analyzing tool would be helpful. The tool should support the following kinds of operations: a) “M X Y”, meaning that we think that the characteristics of spam X and Y are the same. Note that the relationship defined here is transitive, so relationships (other than the one between X and Y) need to be created if they are not present at the moment. b) “S X”, meaning that we think spam X had been misidentified. Your tool should remove all relationships that spam X has when this command is received; after that, spam X will become an isolated node in the relationship graph. Initially no relationships exist between any pair of the junk emails, so the number of distinct characteristics at that time is N. Please help us keep track of any necessary information to solve our problem.

Input

There are multiple test cases in the input file. Each test case starts with two integers, N and M (1 ≤ N ≤ 105 , 1 ≤ M ≤ 106), the number of email samples and the number of operations. M lines follow, each line is one of the two formats described above. Two successive test cases are separated by a blank line. A case with N = 0 and M = 0 indicates the end of the input file, and should not be processed by your program.

Output

For each test case, please print a single integer, the number of distinct common characteristics, to the console. Follow the format as indicated in the sample below.

Sample Input

5 6

M 0 1

M 1 2

M 1 3

S 1

M 1 2

S 3

3 1

M 1 2

0 0

Sample Output

Case #1: 3

Case #2: 2

題目分析:有N封郵件, 然後又兩種操作,如果是M X Y , 表示X和Y是相同的郵件。 如果是S X,那麼表示對X的判斷是錯誤的,X是不屬於X當前所在的那個集合,要把X分離出來,讓X變成單獨的一個。

程式碼如下:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
 
int per[2200000];
int vis[2200000];
int n, m, id;
char str[5];
 
int find(int x){
	if(x == per[x])
		return x;
	else return per[x] = find(per[x]);
}
 
void jion(int x, int y){
	int fx = find(x);
	int fy = find(y);
	if(fx != fy)
		per[fy] = fx;
}
 
void del (int x){
	per[x] = id ++;
}
 
int main (){
	int a,b,c;
	int k = 1;
	while(scanf("%d%d", &n, &m), n || m){
		id = n + n; 
		for(int i = 0; i < n; ++i)
			per[i] = i + n;//虛擬父節點 
		for(int i = n; i <= n + n + m; ++i)//n+n+m: 最多可能刪除m個節點
			per[i] = i;
			
			while(m--){ 
			scanf("%s", &str);
			if(str[0] == 'M'){
				scanf("%d%d", &a, &b);
				 jion(a,b);
			}
			else{
				scanf("%d", &c);
				del(c);
			}
		} 
		int ans = 0;
		memset(vis, 0 ,sizeof(vis));
		for(int i = 0; i < n; ++i){
			int x = find(i);
			if(!vis[x]){
				ans++;
				vis[x] = 1;
			} 
		}
		printf("Case #%d: %d\n", k++, ans);
	}
	return 0;
}

2.簡單的種類並查集

解決方案:設兩個陣列 father[]和relation[]。分別表示該結點的父親結點和該結點與其父親結點之間的關係。

Description 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!

題目分析:一共n個蟲子,編號1-n。一共t組資料。每組資料開始給出n和描述n個蟲相戀關係的m組資料。問你其中是否存在gay蟲。

far[]陣列儲存結點集合,rel[]陣列儲存集合中結點與該父親結點的關係,在此題中用rel[]=1表示跟父親結點異性,rel[]=0表示跟父親結點同性。如果兩個蟲子屬於一個集合(有相同的根節點)且rel[]陣列也相等,說明兩蟲的性別相同,此時兩蟲相戀,屬於同性戀。(另外,在路徑壓縮和合並結點過程中rel[]一直是在動態更新的。具體更新公式請參考大牛文章:https://blog.csdn.net/niushuai666/article/details/6981689

#include <cstdio>
#include<cstring>
using namespace std;
int far[1000005],rel[1000005];
int flag=0;
int find_far(int x){
    if(x==far[x])return far[x];
    int tt=find_far(far[x]);
    rel[x]=(rel[far[x]]+rel[x])%2;
    far[x]=tt;
    return far[x];
}
void Union(int a,int b){
    int fa=find_far(a);
    int fb=find_far(b);
    if(fa==fb){
        if(rel[a]==rel[b])
            flag=1;
            return;
    }
    far[fa]=fb;
    rel[fa]=(rel[a]-rel[b]+1+2)%2;
}
int main()
{
    int t,ans=1;
    scanf("%d",&t);
    while(t--){
        int n,m;
        scanf("%d%d",&n,&m);
        flag=0;
        for(int i=0;i<=n;++i){
            far[i]=i;
            rel[i]=0;
        }
        int a,b;
        while(m--){
            scanf("%d%d",&a,&b);
            if(flag)continue;
            Union(a,b);
        }
        printf("Scenario #%d:\n",ans++);
        if(flag){
            printf("Suspicious bugs found!\n");
        }else{
            printf("No suspicious bugs found!\n");
        }
        printf("\n");
    }
    return 0;
}

3.種類並查集的升級

Description

動物王國中有三類動物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),輸出假話的總數。 

Input

第一行是兩個整數N和K,以一個空格分隔。  以下K行每行是三個正整數 D,X,Y,兩數之間用一個空格隔開,其中D表示說法的種類。  若D=1,則表示X和Y是同類。  若D=2,則表示X吃Y。

Output

只有一個整數,表示假話的數目。

Sample Input

100 7
1 101 1 
2 1 2
2 2 3 
2 3 3 
1 1 3 
2 3 1 
1 5 5

Sample Output

3

題目分析:這道題是上一道題的進階版,在此題中rel[]存放三個值rel【i】=0表示同類,rel【i】=1表示根節點吃i,rel【i】=2表示i吃根節點。由題目資訊可知,輸入的D  x  y ,x與y的關係為d-1.根據前面大牛的文章可以推出rel【i】陣列如何更新。與某些真話衝突的話是假話的條件為far【x】==far【y】且rel【y】-rel【x】!=(d-1)。

#include <cstdio>
#include<algorithm>
#define maxn 50005
using namespace std;
int far[maxn],rel[maxn];
int ans=0;
int find_far(int x){
    if(far[x]!=x){
        int pa=find_far(far[x]);
        rel[x]=(rel[far[x]]+rel[x])%3;
        far[x]=pa;
    }
    return far[x];
}
void Union(int d,int a,int b){
    int fa=find_far(a);
    int fb=find_far(b);
    if(fa==fb){
        if((rel[b]-rel[a]+3)%3!=(d-1)){
            ans++;
        }
        return ;
    }
    far[fb]=fa;
    rel[fb]=(rel[a]-rel[b]+d-1+3)%3;
}
int main()
{
    int n,k;
    int d,x,y;
    scanf("%d%d",&n,&k);
    for(int i=0;i<=n;++i){
        far[i]=i;
        rel[i]=0;
    }
    ans=0;
    while(k--){
        scanf("%d%d%d",&d,&x,&y);
        if(x>n||y>n||(d==2&&x==y)){
            ++ans;
            continue;
        }
        Union(d,x,y);
    }
    printf("%d\n",ans);

    return 0;
}