食物鏈
- 描述
-
動物王國中有三類動物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),輸出假話的總數。- 輸入
-
第一行是兩個整數N和K,以一個空格分隔。
以下K行每行是三個正整數 D,X,Y,兩數之間用一個空格隔開,其中D表示說法的種類。
若D=1,則表示X和Y是同類。
若D=2,則表示X吃Y。 - 輸出
- 只有一個整數,表示假話的數目。
- 樣例輸入
-
100 7 1 101 1 2 1 2 2 2 3 2 3 3 1 1 3 2 3 1 1 5 5
- 樣例輸出
-
3
這題算是對並查集應用的一個總結吧!
參考一另篇博客http://blog.csdn.net/c0de4fun/article/details/7318642,大體思想:
1、如果兩個物種有聯系,不管是吃,被吃還是同類,它們之間應該是有一條徑路可達的,
也就是它們在一個合集中。
2、如果a,b有關系,b,c有關系,那么a,c之間的關系式可以通過兩者的關系推出來的。
OK,下面圍繞着上面的兩個思想來逐一拆分。
首先就是怎么把有關系的物種放到同一個合集中去,這就要需用到並查集了。每一次入輸d,x,y,
也就是相當於x,y之間有一條權為d的徑路。先忽略這個權值,直接斟酌路徑,那並查集的路徑建立就
不用我說了。一個parent數組,parent[i]表現從parent[i]到i有一條徑路。OK,那不同的物食圈就構
成了一個連通區域。每個連通區域都有一個根點節。下面斟酌怎么處理這個權
先說點數學的貨色,任何一種偏序關系都足滿自反、對稱、傳遞。
自反:自己跟自己滿足偏序關系。
對稱:a,b的偏序關系為r,則b,a的偏序關系為~r.表現求反。
傳遞:a,b的偏序關系為r1,b,c的偏序關系為r2,a,c的偏序關系為r1+r2.為了便利,用一個relation數組來維護這個權值。relation[i]表現的是i在所的連通區域的
根點節到i的關系。先略忽這個關系數組的維護過程,把團體的思緒理清晰。如果有兩個物種加進來,
就有兩種情況,要么它們在同一個連通集里頭。要么不在同一個連通集里頭。一、兩者在同一個連通集里頭:
1、新加的關系表明x,y是同類,那么它們兩個分別到連通區域根點節的關系應該是一樣的,
要不就矛盾了。(記為case1)
2、如果新加的關系表明x,y不是同類,那么在當前參加y,x相對根節點的關系和x本來相對根點節的
關系應該是不變的,否則就矛盾了。(記為case2)二、兩者在不同的連通集里頭:就直接連接兩個連通集就能夠了。(記為case3)
路徑壓縮處理:
由於后來物種會越來越多,我們不希望食物鏈拉的很長,所以會盡可能的讓全部的點節都直接和根節點
相連。這樣整個連通的圖就有點呈現出星形。怎么維護關系數組:
數組里頭的每個元素的取值要么是0(同類),要么是1(父吃子),要么是2(子吃父)。至於為什么要
這么設置(因為題目中1表示同類,而我們定義0表示同類,相對都應該減一,所以題目中的2表示父吃子在我們這里
應該是2-1=1,,1表示父吃子),參考一另篇博客http://blog.csdn.net/c0de4fun/article/details/7318642,
這里是不能隨便定義的。設前面的數據我已經處理好了,現在要處理d,x,y.為了敘說的便利,
記relation[x]為x根->x.那么在現就有三種情況:
case1:(同一個集合且同類)
這種情況x根與y根雷同。如果x根->x與y根->y不同,表明x,y不是同類,與d=1矛盾。
case2:(同一個集合但不同類)
這種情況x根與y根雷同。如果參加y之后,(x根->x) = (x根(即y根)->y + y->x),如果新求出來
的關系與本身已有的x根->x的關系不同,則矛盾。
case3:(在不同集合中)
這種情況x根與y根不同。由於這里添加的是x到y的一條有向邊。將y根的父點節設置為x根,更新y根父點
節到x根的關系,即x根->y根=x根->x+x->y+y->y根,由於這里都是有向邊,所以更新關系的時候注意關
系的方向。這里需要注意,我們只更新了兩個根之間的關系,x根與原來的y所在的連通區域里頭的節點
的關系都沒有更新,這就是為什么要在一開始判斷之前就要調用Find函數,更新每個點節到其根點節的
關系。初始條件:
有了這個遞推,就好辦了。初始條件parent就是並查集一般的初始條件,父點節於等自己。由於初始的
時候父節點是自己,當然自己跟自己的關系肯定是同類咯,也就是relation[i]=01 #include <cstdio> 2 #include <iostream> 3 4 using namespace std; 5 6 const int N = 50005; 7 int father[N]; 8 int relation[N];//根點節到點節的關系 9 10 void init(int n) 11 { 12 for(int i = 0; i <= n; ++i) 13 { 14 father[i]= i; 15 relation[i] = 0; 16 } 17 } 18 //更新的步調,先將當前點節與其根點節相連,然后更新其與根點節的關系 19 //當前節點x與根節點r的關系更新的方法: 20 // (x與其父點節的關系+其父點節的關系與根點節的關系)%3 21 //所以在更新節點x的數據之前需要更新其父節點的數據,這是find為什么搞成遞歸函數的原因 22 //其更新的次序是從根節點開始往下,始終到當前點節x的父點節。 23 int find(int x) 24 { 25 if(x != father[x])//不是根點節 26 { 27 int temp = father[x]; 28 //將當前點節的父點節設置為根點節 29 father[x] = find(temp); 30 //更新當前點節與根點節的關系,由x->x父和x父->父根的關系失掉x->父根的關系 31 //所以在這之前必須更新其父點節與根點節的關系 32 relation[x] = (relation[x] + relation[temp]) % 3; 33 } 34 return father[x]; 35 } 36 37 int main() 38 { 39 int n, m, x, y, d, fx, fy, cnt; 40 41 while(~scanf("%d %d", &n, &m))//POJ上只要需一次入輸,所以不要需while循環 42 { 43 cnt = 0; 44 init(n); 45 for(int i = 0; i < m; ++i) 46 { 47 scanf("%d %d %d", &d, &x, &y); 48 if(x > n || y > n) 49 { 50 ++cnt; 51 continue; 52 } 53 if(d == 2 && x == y) 54 { 55 ++cnt; 56 continue; 57 } 58 fx = find(x); 59 fy = find(y); 60 if(fx == fy)//屬於同一個子集 61 { 62 //如果x、y是同類,那么他們相對根點節的關系應該是一樣的 63 if(d == 1 && relation[x] != relation[y]) 64 ++cnt; 65 //如果不是同類,加入x與y的關系之后,x相對根點節的關系(x根->y,y->x(即3-(d-1)=2).即x根->x)應該是不變的 66 //這里d=2表示x - y = 2-1=1;而y->x=3-(x->y)=3-1=2; 67 if(d == 2 && relation[x] != (relation[y] + 2)%3) 68 ++cnt; 69 } 70 else//合並兩個連通區域 71 { 72 father[fy] = fx;//y根的父點節更新成x根 73 //(d-1)為x與y的關系,3-relation[y]是y與y的根點節的關系,注意方向,relation[x]是其根點節與x的關系 74 //x根->x,x->y,y->y根:即x根->y根 75 relation[fy] = (relation[x] + (d-1) + (3-relation[y])) % 3;//注意這里只更新的是fy相對於根的關系 76 } 77 } 78 printf("%d\n", cnt); 79 } 80 return 0; 81 }