並查集的拓展域
題目描述
動物王國中有三類動物 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 句話有的是真
的,有的是假的。當一句話滿足下列三條之一時,這句話就是假話,否則就是真話。
• 當前的話與前面的某些真的話衝突,就是假話
• 當前的話中 X 或 Y 比 N 大,就是假話
• 當前的話表示 X 吃 X,就是假話
你的任務是根據給定的 N 和 K 句話,輸出假話的總數。
分析:
這道題裡,關係就有很多種,x與y可能是同類,也可能是x吃y,或者y吃x;
我們在做並查集的操作時,是把相互關聯的點合併,每次有新的操作時,判斷有沒有衝突,然後再做下一步;這道題如果我們只是單純的把x當做一個點,那我們沒法兒維護這中間的關係。想一下,x的同類是一種關係,我們需要把他們合併,x的所有天敵是一類,也需要合併,x的食物也是一類,同樣需要合併。但是這三種合併又不能用一個點來進行。所以我們把每個點拆開,變成三個點。分別是Xself,Xeat,Xenemy。
如果x與y是一類,那麼Xself與Yself合併,Xeat與Yeat合併,Xenemy與Yenemy合併。
如果x吃y,那麼Xeat與Yself合併,Yenemy與Xself合併,Xenemy與Yeat合併(因為只有三種動物,如果x吃y,y吃z,那麼z吃x);
在執行合併之前要先判斷有沒有衝突,關於x=y有兩種資訊衝突:
1、x吃y;即Xeat=Yself
2、y吃x;即Yeat=Xself
關於x吃y有兩種衝突:
1、x與y是一類,即Xself=Yself
2、y吃x,即Yeat=Xself
如果資訊有衝突,那麼這句話就是假話。否則,執行合併;
程式碼:
#include<bits/stdc++.h> using namespace std; int n,m,ans,q,x,y,a,b,Xself,Yself,Xeat,Yeat,Xen,Yen,fa[150010]; int get(int x) { if(fa[x]==x) return fa[x]; return fa[x]=get(fa[x]); } void Merge(int a,int b) { fa[get(a)]=get(b); } int main() { scanf("%d %d",&n,&m); for(int i=1;i<=3*n;i++) fa[i]=i; for(int i=1;i<=m;i++){ scanf("%d %d %d",&q,&x,&y); Xself=(x-1)*3+1;Xeat=(x-1)*3+2;Xen=x*3; Yself=(y-1)*3+1;Yeat=(y-1)*3+2;Yen=y*3; if(x>n||y>n){ ans++;continue; } if(q==1){ if(get(Xself)==get(Yeat)||get(Xeat)==get(Yself)){ ans++;continue; } Merge(Xself,Yself); Merge(Xen,Yen); Merge(Xeat,Yeat); } else{ if(get(Xself)==get(Yself)||get(Yeat)==get(Xself)){ ans++;continue; } Merge(Xeat,Yself); Merge(Xself,Yen); Merge(Xen,Yeat); } } cout<<ans<<endl; return 0; }