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;
}