1. 程式人生 > >POJ 1182 食物鏈 並查集+偏移向量

POJ 1182 食物鏈 並查集+偏移向量

引言:

一般的並查集都是把同一類的點放在一棵樹上。但是這道題給出的是一些點的相對關係,就不能按照這樣的規則建樹了。應該把能夠確定相對關係的點放到一棵樹上。每個節點除了自身的值之外,還應該有一個域,或者說是偏移量,它表示的是這個點和其父節點之間的關係。所以當我們要改變一個節點的父節點時,還要同時更新這個點和父節點的關係。

什麼時候一個點的父節點會改變呢?

1. 路徑壓縮

2.兩棵樹合併

正文:

題意很簡單,不再贅述。大致解題思路就是:我們在輸入的過程中不斷的維護並查集,如果輸入的兩個動物在同一個集合內,也就是他們的關係我們已經確定了。那麼就把我們確定的關係和輸入的關係比較,相等則為真,反之為假。如果兩個動物不在一個集合內,即他們的關係我們還不確定,所以這句話肯定是真。同時我們還要把兩個動物所在的集合合併。假設x是y的父親,如果y的偏移量是0,代表x和y是同類,如果等於1,代表y被x吃,如果為2,代表x吃y。輸入的值是d,x,y。d-1就是x和y的關係,d-1==0,y和x是同類。d-1==1, y被x吃。

上文中說過了,路徑壓縮的時候父節點會變,所以我們要改變當前節點的偏移量。因為是遞迴壓縮,所以在把當前節點作為根節點的兒子時,它之前的父親已經成為根節點的兒子了。假設原來root->a->x->y,壓縮之後就成了root->a, root->x, root->y。我們知道了x->y時y的偏移量,root->x時x的偏移量,該怎麼求root->y的偏移量呢?用y.relation表示y的偏移量。則y.relation = (x.relation + y.relation) %3。

怎麼算的呢?這個也就是當我們已知爺爺和父親的關係a,父親和兒子的關係b,求爺爺和兒子的關係c。我們可以窮舉出所有可能,然後總結出上面的式子。

另外在樹合併的時候也要改變偏移量。假設輸入x,y。我們發現他們不在同一個集合。x的根是rootx,y的根節點是rooty。我們把rootx作為rooty的父節點。所以同時我們要更新rooty.relation。因為在之前我們執行過一次find_root,所以也進行了路徑壓縮。所以現在x,y到他們根的距離只有1,即根就是x,y的父親。

另外補充一點,假如x作為y的父親時,y的偏移量是a,那麼y作為x的父親時,x的偏移量就是(3 - a)%3。

現在我們來計算rooty.relation。先把x作為y的父親,y的偏移量是d-1,y作為rooty的父親,rooty的偏移量是, (3 - y.relation) % 3, 所以x當rooty父親時,rooty的偏移量就是

((d-1) + (3 - y.relation) )%3,這裡運用了同餘公式。我們已知了rootx作為x父親時,x的偏移量,所以我們就可以計算出,rootx作為rooty父親時,rooty的偏移量是

(x.relation + (d - 1) + (3 - y.relation))%3,這裡運用了同餘公式。

具體程式碼如下:

#include <algorithm>
#include <iostream>
#include <iterator>
#include <sstream>
#include <fstream>
#include <istream>
#include <ostream>
#include <complex>
#include <cstring>
#include <utility>
#include <cstdlib>
#include <cstdio>
#include <vector>
#include <string>
#include <cctype>
#include <ctime>
#include <cmath>
#include <queue>
#include <stack>
#include <list>
#include <new>
#include <set>
#include <map>

using namespace std;

const int maxn = 50000 + 5;
const int INF = 0x3f3f3f3f;
int d;
struct Animal
{
    int id;
    int parent;
    int relation;
};
Animal a[maxn];

int find_root(int x)
{
    if (a[x].parent == x)
        return x;
    else
    {
        int temp = a[x].parent;
        a[x].parent = find_root(temp);
        a[x].relation = (a[x].relation + a[temp].relation) % 3;
        return a[x].parent;
    }
}

void unite(int x, int y)
{
    int rx = find_root(x);
    int ry = find_root(y);
    if (rx != ry)
    {
        a[ry].parent = rx;
        a[ry].relation = ((3 - a[y].relation) + (d - 1) + a[x].relation) % 3;
    }
}

void initial()
{
    for (int i = 0; i < maxn; i++)
    {
        a[i].id = i;
        a[i].parent = i;
        a[i].relation = 0;
    }
}

int main()
{

    //freopen("1.txt", "r", stdin);
    int n, k;
    scanf("%d%d", &n, &k);
    int ans = 0;
    initial();
    for (int i = 0; i < k; i++)
    {
        int A1, A2;
        scanf("%d%d%d", &d, &A1, &A2);
        if (A1 > n || A2 > n)
            ans++;
        else if (A1 == A2 && d != 1)
            ans++;
        else if (find_root(A1) != find_root(A2))//不在同一個集合
            unite(A1, A2);
        else
        {
            switch(d)
            {
                case 1:
                {
                    if (a[A1].relation != a[A2].relation)
                    //因為之前進行過路徑壓縮,所以A1,A2的父親節點都是根節點
                    //如果A1,A2是同類,那麼他們的relation應該是相等的
                        ans++;
                    break;
                }
                case 2:
                {
                    if(((a[A2].relation + 3 - a[A1].relation) % 3 ) != 1)
                    //稍微推導一下就知道這是代表A1作為A2父親時,A2的偏移量
                        ans++;
                    break;
                }
            }
        }
    }
    printf("%d\n", ans);
    return 0;
}