1. 程式人生 > >並查集------E - 食物鏈

並查集------E - 食物鏈

動物王國中有三類動物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),輸出假話的總數。
Input
第一行是兩個整數N和K,以一個空格分隔。
以下K行每行是三個正整數 D,X,Y,兩數之間用一個空格隔開,其中D表示說法的種類。
若D=1,則表示X和Y是同類。
若D=2,則表示X吃Y。
Output
只有一個整數,表示假話的數目。
Sample Input
100 7
1 101 1
2 1 2
2 2 3
2 3 3
1 1 3
2 3 1
1 5 5
Sample Output
3

這道題是個好題。。。。。
既然要求假話,肯定是說的話跟以前說的存在矛盾,比如A->B,B->A,這樣很明顯第二句話是假話了,跟第一句話衝突,那麼我們應該怎麼去查詢這種衝突呢,我們知道查詢兩個節點之間關係最快的方法就是並查集,只要把一些節點歸為一個集合,查詢起來是非常方便的,那麼這道題是不是也可以這麼做呢?答案是肯定的,不過我們除了要記錄每個節點的根節點以外,還要記錄與根節點的關係。
這個部落格講的非常明白了:https://www.cnblogs.com/liuxin13/p/4668205.html
生物之間的關係,0代表二者是同類,1代表A吃B,2代表A被B吃
產生關係(認定為正確的話)的兩個動物就進行合併,查詢的時候,兩個的根節點不相同,說明未產生關係,所以將兩個合併;如果兩個根節點相同,說明這兩個動物之間有關係,所以需要判斷是否說的錯的,如果(r[y] + d - 1)% 3 != r[x]。。。則說明這個說的是錯的,具體原因請看上面部落格。。。。。。
主要是並查集如何路徑壓縮,以及合併,路徑壓縮時用遞迴的寫法好,主要是計算路徑上的r值,合併是合併x與y的根節點即可。。。。
在這裡插入圖片描述


找出來他們的根節點ra 和 rb
我們想求出來 ra->rb ??
根據向量加法我們得出來
ra->rb = (ra->b+b->rb) % 3
ra->b = (a->b – a->ra + 3) % 3
合併一下兩個公式得出來
ra->rb = (a->b - a->ra + 3 + b->rb) %3
有了這個關係我們就可以合併兩個集合了,當然在做路徑壓縮的時候也不能忘了關係壓縮。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<map>
#include<queue>
#include<cmath>
using namespace std;
typedef long long LL;
const int inf = 0x3f3f3f3f;
const double eps = 1e-8;
const double PI = acos(-1);
#define pb push_back
#define mp make_pair
#define fi first
#define se second
const int N = 50005;

int b[N];
//記錄與根節點的關係
int r[N];

int join1(int x)
{
    int k = b[x];
    if(b[x] != x){
        b[x] = join1(b[x]);
        r[x] = (r[x] + r[k]) % 3;
    }
    return b[x];
}

int main()
{
    int n,k;
    scanf("%d %d",&n,&k);
    int d,x,y;
    for(int i = 1;i <= n;++i){
        b[i] = i;
        r[i] = 0;
    }
    int sum = 0;
    for(int i = 0;i < k;++i){
        scanf("%d %d %d",&d,&x,&y);
        int p = join1(x),q = join1(y);
        if(x > n || y > n || (d == 2 && x == y)){
            sum++;
            //p == q代表p與q已經產生關係了
        }else if(p == q && (r[y] + d - 1) % 3 != r[x]){
            sum++;
        }else if(p != q){
            b[p] = q;
            r[p] = (d - 1 - r[x] + r[y] + 3) % 3;
        }
    }
    printf("%d\n",sum);
    return 0;
}