並查集應用 —— POJ 1182 食物鏈

對應 POJ 題目:點擊打開鏈接

食物鏈
Time Limit: 1000MS   Memory Limit: 10000K
Total Submissions: 54927   Accepted: 16104

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

題意:中文題,不多說


思路:

        開始時把同一類的動物並在相同的集合往死裏想,做了一堆無用功;首先要正確理解的一點是:要把相互之間有關係(喫與被喫關係,同類關係)的,或者他們之間的關係可以通過集合所構成的樹推導出來的動物(比如x, y)並在相同的集合

                                                        


        如上圖,在這棵以 5 爲根的樹中,其中每個結點之間都存在關係,比如 1 和 2 之間,他們的關係要麼是1 喫 2; 要麼是  2 喫 1 ;要麼1 和 2 是同類。確定他們之間關係的方法將重點講。

        我們用 fa[x] = y 表示 x 的父親結點爲 y;然後用一個rela[] 數組表示 x 與 fa[x] 的關係。

        rela[x] = 0 表示 x 與 fa[x] 是同類,

        rela[x] = 1 表示 x 喫 fa[x],

        rela[x] = 2 表示 fa[x] 喫 x。


        看這樣一顆3個結點的樹,

       (圖1)             (圖2)   

        假如此時rela[x] = 1;relax[y] = 1;relax[z] = 0(也只能等於0,因爲它是根,自己跟自己是同類)

        即是 x 喫 y ,y 喫 z ;由這兩點再根據題目意思還可以知道 z 喫 x

        當我們對 x 調用 fx = Find(x) 時,這時必須知道什麼是並查集的路徑壓縮,不知道的先弄懂再往下看;

        我們就會把(圖1 )變成(圖2),但此時rela[x] = 1 就錯了(這樣就是 x 喫 z ,與原意 z 喫 x 不符)

        所以在壓縮路徑的時候要修改 rela[x] 的值,怎樣修改?!

        其實我們可以根據 x 對 y 的關係,以及 y 對 z 的關係,推導出 x 對 z 的關係。枚舉這個過程就是:

        rela[x]        rela[y]          rela[z]     |     修改後的 rela[x]

           0                0                  0                                  0

           0                1                  0                                  1

           0                2                  0                                  2

           1                0                  0                                  1

           1                1                  0                                  2

           1                2                  0                                  0

           2                0                  0                                  2

           2                1                  0                                  0

           2                2                  0                                  1

        通過枚舉所有情況我們發現

        修改後的 rela[x] = (rela[x] + rela[y]) % 3;

        因此在令 z = fa[x] 之前加上這句:rela[x] = (rela[x] + rela[y]) % 3  就能正確描述結點之間的關係。 

-------------------------------------------------------------------------------------------------------------------------------------------

        這樣對於一個操作 d  x  y;我們可以這樣處理:

        令 fx = Find(x);   fy = Find(y);    如果 fx == fy ;即 x 與 y 在同一個集合裏。這好辦,根據路徑壓縮我們會得到這樣的一棵樹:

                     

        即 x 與 y 都直接指向根結點(也就是集合的代表)。這時我們知道的是 x 對 root 的關係 rela[x] 和 y 對 root 的關係 rela[y];問題是我們要知道的是 x 對 y 的關係,因爲我們要以此來判斷 d   x   y 是否正確。

        如果我們把上圖想象成(只是想象,實際並不進行這樣的操作):

                              即改變了root 與 y 的父子關係

        rela[x] 沒有變,我們不去動它;但 rela[root] 不再是 0 ,這裏我直接說了,通過枚舉我們可以知道 rela[root] = 3 - rela[y]。接下來有沒有熟悉的感覺,就是上面(圖1)的那個過程;這裏就可以確定 x 與 y 的關係了。即 x 對 y 的關係 rxy = (rela[x] + rela[root]) % 3;展開就是 rxy = (rela[x] + (3 - rela[y])) % 3;

        接下來如果 rxy != d - 1 的話,那 d   x   y 這句話就是假的(把 d = 1 和 d = 2 代入你會發現的確是這樣)


        如果 fx != fy 呢?這就說明 x 與 y 不在同一個集合裏面  。這更好辦,說明 x 與 y 還沒有任何關係,你怎樣說都是對的。麻煩的是定義了x 與 y 的關係之後(比如 1  x  y  或 2  x  y),你要把 x 所在的集合跟 y 所在的集合合併,使得 x 與 y 存在關係。

        我們通過 fx = Find(x) 和 fy = Find(y) 同樣可以得到這樣圖:

          

           爲了方便觀察我們可以簡化這個圖:

                         

        這裏我們把 x 所在集合接到 y 所在集合裏,即最終令 fa[fx] = fy,即

                          

         但在這之前我們要先確定 fx 對 fy 的關係,即修改 rela[fx]。怎樣修改?我們通過 d  x  y  操作可以知道 x 對 y 的關係爲 d - 1 (d = 1 那 d - 1  = 0 就表示 x 與 y 是同類,d = 2 那 d - 1  = 1 就表示 x 喫 y,跟我們的定義是一樣的);即我們知道了 x 對 y 的關係,y 對 fy 的關係,那根據前面的公式, x 對 fy 的關係 x_fy = ((d - 1) + rela[y]) % 3。到這裏我們可以想象到這樣一個圖:

                          

        這裏我們知道 x 對 fx 的關係 rela[x] 和 x 對 fy 的關係 x_fy;那我們可以像前面那樣改變 x 與 fx 的父子關係,使圖變成這樣:

                        

        相應地可以求 fx 對 x 的關係爲 fx_x = 3 - rela[x];這樣就可以求 fx 對 fy 的關係了!即 fx 對 fy 的關係 fx_fy = (fx_x + x_fy) % 3。把原數代入就是:

         fx_fy = ((3 - rela[x]) + ((d - 1) + rela[y]) % 3) % 3          也就是:

         fx_fy = (2 - rela[x] + d + rela[y]) % 3    

        這樣就可以合併兩個集合了。

        必須歎服!這道題把並查集運用地非常巧妙!

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define N 50010
int fa[N];
int rela[N];

void Init()
{
	int i;
	for(i = 0; i < N; i++){
		fa[i] = i;
		rela[i] = 0;
	}
}

int Find(int x)
{
	int fx;
	if(x == fa[x]) return x;
	fx = Find(fa[x]);
	rela[x] = (rela[x] + rela[fa[x]]) % 3;
	fa[x] = fx;
	return fx;
}

void Union(int x, int y, int d) /* 使 x 所在集合成爲 y 所在集合的子集合 */
{
	int fx, fy;
	fx = fa[x];
	fy = fa[y];
	rela[fx] = ((3 - rela[x]) + (d - 1) + rela[y]) % 3;
	fa[fx] = fy;
}

int main()
{
#if 0
	freopen("in.txt","r",stdin);
#endif
	int n, m, count = 0;
	scanf("%d%d", &n, &m);
	Init();
	while(m--){
		int d, x, y, fx, fy;
		scanf("%d%d%d", &d, &x, &y);
		if(x < 1 || x > n || y < 1 || y > n){
			count++;
			continue;
		}
		fx = Find(x);
		fy = Find(y);
		if(fx == fy){
			if((rela[x] + (3 - rela[y])) % 3 != d - 1) 
				count++;
		}
		else Union(x, y, d);
	}
	printf("%d\n", count);
	return 0;
}





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章