第三次復習了,最經典的並查集
題意:動物王國中有三類動物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),輸出假話的總數。
首先我們要明確並查集的作用:快速判斷兩個數是否同一集合與快速合並兩個集合成一個集合並求出一些節點之間的關系,根據的就是樹的特點:每個孩子節點有且僅有一個父節點。這樣就用數組記錄父節點就還(根就記錄自己),合並操作就是合並兩個根節點,這兒有個優化就是啟發式合並:根記錄孩子個數,並把個數少的並到個數大的上面。
不過我們有其他大招:路徑壓縮,即我們每次查詢的時候都把一條線上的所有節點連接到祖先節點,這樣每次查找都很快(並查集在路徑壓縮之后的時間復雜度是阿克曼函數)。依據就是我們只需要知道多個孩子節點的祖先是否一致就能判斷是否一個集合,不需要知道樹上的結構。我們的權值則是一般記錄此節點與父節點的關系,只要滿足這個關系可以傳遞我們就可以模仿矢量計算來處理權值。
這兒我們要明確是有三種關系的:兩者同類,吃父節點,被父節點吃,所以權值可以用0,1,2表示
注意有個關鍵就是當我們知道x與祖先x1的關系,y與祖先y1的關系,x與y的關系時,求x1與y1的關系時,使用矢量 計算:
x1->x ->y ->y1 計算
/*n個動物 k句話 有一種循環a吃b 吃c c吃a 開始不知道n種動物關系是什么 兩種詢問:d=1 x y為同類 d=2 x吃y 判斷假話條數(關鍵之違背之前的關系) 並查集可以很好解決的滿足區間傳遞關系的區間合並問題,注意一般是多棵樹*/ #include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int Max=50010; int fat[Max],ran[Max]; void Init(int n)//初始化重要 { for(int i=0; i<=n; i++) { fat[i]=i;//初始化都是指向(看做)自己 ran[i]=0;//0同類 1吃父節點 2被父節點吃 } return; } int Find(int x)//找尋父節點+路徑壓縮 { if(x==fat[x]) return fat[x]; int y=Find(fat[x]); ran[x]=(ran[x]+ran[fat[x]])%3;//遞歸后從祖先節點向后到每個孩子來計算 return fat[x]=y;//路徑壓縮 } int Union(int typ,int x,int y)//區間並與查詢 { int x1=Find(x); int y1=Find(y); if(x1==y1)//共父節點才能判斷出關系 { if((ran[x]-ran[y]+3)%3==typ-1) return 0; return 1; } fat[x1]=y1;//連接兩父節點 ran[x1]=(-ran[x]+typ-1+ran[y]+3)%3;//使用類似向量方法來計算權值,雖然題目只有兩個,但是會出現被吃這種情況,所以要變成3種情況,注意一定要處理負數的情況 return 0; } int main() { int n,k,ans; int typ,smt1,smt2; scanf("%d %d",&n,&k); Init(n); ans=0; for(int i=0; i<k; i++) { scanf("%d %d %d",&typ,&smt1,&smt2); if(smt1==smt2&&typ==2) ans++; else if(smt1>n||smt2>n) ans++; else ans+=Union(typ,smt1,smt2); } printf("%d\n",ans); return 0; }
