1182: 食物鏈 (並查集)
阿新 • • 發佈:2019-01-28
題目大意: 中文題目,自己看就好
解題思路:並查集經典中的經典題,在網上看了很多大牛的思路,大部分是增加一個結構體存動物間的關係,結合並查集判斷,但是關係域的更新比較複雜,一下子不太容易理解,這裡推薦一個大牛的解題思路,講解得非常清楚:https://blog.csdn.net/niushuai666/article/details/6981689。
下面來看看另一種方法,想法非常奇特,而且更容易理解:
一般我們都會把一個動物當成一個節點,然後去執行並查集等操作。但是有位大牛另闢蹊徑,給每個動物賦予三個節點(n,2n,3n),這樣就將並查集的節點數量擴充套件到3n,用並查集維護這3n個節點的資訊就行。下面是具體的思路和操作:
1 先來考慮什麼情況下a和b兩個動物是同類:
(1)a吃c,b也吃c,則a和b同類;
(2)a吃c,c吃d,d吃b,則a和b同類;
2 有了上面的基礎,我們還需要巧妙的定義下面的規則:
(1)如果a和b是同類,則a的三個節點(a_n,a_2n,a_3n)分別指向b的對應的三個節點(b_n,b_2n,b_3n),即a_n -> b_n , a_2n -> b_2n , a_3n -> b_3n;
(2)如果a吃b,則a的三個節點(a_n,a_2n,a_3n)分別指向對應b的三個節點(b_n,b_2n,b_3n)的下一個節點,即a_n -> b_2n , a_2n -> b_3n , a_3n -> b_n;
3 所以我們現在可以表示1中提到的兩種同類的情況了:
(1)
(2)
按照上面的規則對每一個動物去建立關係,那麼,如果a和b是同類,肯定有a_n與b_n連通,即有相同的根節點;如果a吃b,肯定有a_n與b_2n連通;如果b吃a,肯定有b_n與a_2n連通。
AC程式碼:
///並查集
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstdlib>
#include<algorithm>
using namespace std;
const int maxn=50005;
int fa[maxn*3];
int n,m;
int ans;
int Find(int x){
int t=x;
while(fa[t]!=t) t=fa[t];
while(x!=t)
{
int temp=fa[x];
fa[x]=t;
x=temp;
}
return t;
}
void Join(int x,int y){
int fx=Find(x),fy=Find(y);
if(fx!=fy)
fa[fx]=fy;
}
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++){
int num,a,b;
scanf("%d%d%d",&num,&a,&b);
if(a<1||a>n||b<1||b>n) {
ans++; continue;
}
if(num==2&&a==b){
ans++;continue;
}
if(num==1){//a,b同類
if(Find(a)==Find(b+n)||Find(b)==Find(a+n)) ans++;//如果a吃b或者b吃a,說明是假話
else {//否則是真話,建立a和b同類的關係
Join(a,b);
Join(a+n,b+n);
Join(a+2*n,b+2*n);
}
}
else if(num==2){//a吃b
if(Find(a)==Find(b)||Find(b)==Find(a+n)) ans++;//如果a,b同類或者b吃a,說明是假話
else {//否則是真話,建立a吃b的關係
Join(a,b+n);
Join(a+n,b+2*n);
Join(a+2*n,b);
}
}
}
printf("%d\n",ans);
return 0;
}