食物鏈
時間限制:1000 ms | 內存限制:65535 KB
poj-1182
描述
動物王國中有三類動物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
這道題很經典,第一次做的帶權的並查集,加深了我對並查集的理解與應用,去網上看的代碼,有篇博客非常簡潔,解題關鍵代碼就只有7.8行的樣子吧。沒有很詳細的注釋,就這7,8行,看了我足足6.7小時,
收獲挺大,我想把這題關鍵的部分寫下來,以后可以看看,順便也許能幫到人吧。
這是我參考的一篇博客:很詳細,但是說得有點太多了,我都看得累了,代碼也有點多了。。。下面的代碼才是我說的只有7行關鍵的簡潔代碼,我寫的類似與那篇博客
http://blog.csdn.net/c0de4fun/article/details/7318642
這道題的代碼比較精巧,先說總體思路
剛拿到這題,我想的是能不能將之化為3個並查集ABC,然后每個並查集頭結點都記錄着這類動物是什么屬性,然后發現,根本行不通,因為不好確定到底是哪種屬性的動物
思路:這個的思路是將所有說到的動物都放在一個並查集,就是說只要有關系,就連在一起。
1.find_head()找了頭結點還壓縮了路徑到只有兩層,這樣就很容易判斷兩者的關系了,壓縮路徑時,就要適當的改變關系了
2.如果是同一個頭結點的話,對頭結點的關系要推個式子,下面就說
3.如果不在一個頭結點,就說明沒有關系嘛。肯定是真話,然后直接連起來就行了。連起來要又需要推導個式子,難住我很久。。。
f[N]是存 N 號動物的父節點的編號,r[N]是對父節點的關系 0 同類,1 被父節點吃,2 吃父節點
find_head()函數遞歸不但找了頭結點,還將搜索路徑上的節點都壓縮路徑,將它直接連接到了頭結點,對遞歸熟悉的不難理解
關鍵在於確定對 爺爺節點 的那個公式上,窮舉一下,然后就能很輕松的看出來的,關系有點像個三角形
r[f[x]] r[x]
父親對爺爺 兒子對父親 兒子對爺爺
0 0 0 所以,兒子對爺爺就可以推出這個式子 (r[x] + r[f[x]] )%3
0 1 1
0 2 2
1 0 1
1 1 2
1 2 0
2 0 2
2 1 0
2 2 1
圖:
用這個式子就解決了壓縮路徑的關系轉換
然后到了將並查集聯合這個函數,也就是 3 的情況,這是最難的部分:
前面說了,用find_head()找頭結點,並且直接連上頭結點,然后判斷兩個的頭結點是否相等,
1,相等就判斷關系是否矛盾,因為只有兩層,枚舉一下,容易發現規律,如果 (r[y]-r[x]+3)%3!=d-1 代表假話
或者你足夠理解那個find_head()的那個公式 3-r[x] 是 fx 對 x 的關系與 r[y] 相加一下就是 y 對 x 的關系看是否等於d-1。
2,不同頭結點就有點麻煩,像個四邊形的關系,所以化成兩個三角形。
3-r[y]是 y 的頭結點對 y 的關系,這個好理解吧,
d-1是 y 對 x 的關系,因為 d 原本是 x 對 y 的關系,減 1 正好反過來。
然后用上我們前面的find里面的那個對父節點的公式, (r[x] +r[f[x]] )%3 ,帶進去,就得到 fy 對 x 的關系了,
然后再加上 x 對 fx 的關系就得到了fy 對 fx 的關系, 這不就分成了2個三角形,得到了 r[fy] = 什么。,
不懂,看圖:已知標出,要求綠色,先求斜的,再求 r[fy]
寫完博客,發現更加理解這題了,也更加理解怎么推導公式啥的了,有收獲。

1 #include <stdio.h>
2
3 int const N=50005; 4 int f[N]; 5 int r[N]; 6
7 int find_head(int x) 8 { 9 int fx=x; 10 if (x!=f[x]) 11 { 12 fx=find_head(f[x]); 13 r[x]=(r[x]+r[f[x]])%3; //這是窮舉可以輕松看出來的公式,壓縮路徑
14 f[x]=fx; 15 } 16 return f[x]; 17 } 18
19 int Union(int d,int x,int y) 20 { 21
22 int fx=find_head(x); 23 int fy=find_head(y); 24 if (fx!=fy) 25 { 26 f[fy]=fx; 27 r[fy]=(3-r[y]+d-1+r[x])%3; //很重要的一個公式,由find里面的公式推導過來
28 return 0; 29 } 30 if ((r[y]-r[x]+3)%3!=d-1) return 1; 31 return 0; 32 } 33
34 int main() 35 { 36 int n,k; 37 int d,x,y; 38 int ans=0; 39 scanf("%d%d",&n,&k); 40 for (int i=1;i<=n;i++) 41 { 42 f[i]=i; 43 r[i]=0; 44 } 45 while (k--) 46 { 47 scanf("%d%d%d",&d,&x,&y); 48 if (x>n||y>n||(x==y&&d==2)) 49 { 50 ans++; 51 continue; 52 } 53 if (Union(d,x,y)) ans++; 54 } 55 printf("%d\n",ans); 56 return 0; 57 }