POJ-1182 食物鏈(NYOJ-207)


 

食物鏈

時間限制: 1000 ms  |  內存限制: 65535 KB
難度: 5
 
描述
動物王國中有三類動物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]=0

 1 #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 }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM