1. 程式人生 > >1182 食物鏈 並查集經典

1182 食物鏈 並查集經典

          思路:設r(x)表示節點x與根結點的關係,px表示x的根結點。記錄每個節點與其父節點的關係,就能很方便知道每個節點以及和它的父節點的關係。

struct node{
	int par; //父親節點
	int rel; //與父節點的關係 
}a[maxn];
//關係:0表示同類, 1表示父節點吃子節點, 2表示子節點吃父節點 

      現在給定節點x和y,它們的關係是rel,如何判斷這句話是真還是假?

利用find函式找到它們的根結點px和py,以及它們和各自根結點的關係r(x)和r(y),如果px!=py說明x和y沒有位於同一集合,那麼這句話不會和任何話發生衝突,即這一定是真話,然後合併兩個集合;另一種情況就是px==py,說明x和y位於同一集合,現在已經得到x(x)和r(y),那麼如何知道x和y的關係呢?我先介紹一下find函式的實現:

int find(int x, int &r) {
	if(a[x].par == x) {
		r = x;
		return a[x].rel;
	}
	int y = find(a[x].par, r);
	a[x].par = r; //路徑壓縮 
	return a[x].rel = (a[x].rel + y) % 3;
}

find函式每次返回當前節點與根結點的關係,那麼在已知當前節點和它的父親節點關係,父親節點和根結點的關係,很容易得到當前節點與跟節點的關係r(x) = ( r(a[x].par) + a[x].rel) % 3

同樣對於上面的問題,只需要變換一下x、root、y三者的關係,也能求得x和y的關係r(x, y),如果r(x, y) == rel,說明是真話,否則假話。

注意:所有的關係轉換都利用了find函式中的思想,請保證明確關係轉換才能看懂unionset函式。

AC程式碼

#include <cstdio>
#include <cmath>
#include <cctype>
#include <algorithm>
#include <cstring>
#include <utility>
#include <string>
#include <iostream>
#include <map>
#include <set>
#include <vector>
#include <queue>
#include <stack>
using namespace std;
#pragma comment(linker, "/STACK:1024000000,1024000000") 
#define eps 1e-10
#define inf 0x3f3f3f3f
#define PI pair<int, int> 
typedef long long LL;
const int maxn = 50000 + 5;
struct node{
	int par; //父親節點
	int rel; //與父節點的關係 
}a[maxn];
//關係:0表示同類, 1表示父節點吃子節點, 2表示子節點吃父節點 

int find(int x, int &r) {
	if(a[x].par == x) {
		r = x;
		return a[x].rel;
	}
	int y = find(a[x].par, r);
	a[x].par = r; //路徑壓縮 
	return a[x].rel = (a[x].rel + y) % 3;
}

bool unionset(int x, int y, int rel) {
	int px, py;
	int rx = find(x, px), ry = find(y, py);
	if(px != py) { //位於同一集合 
		ry = (3 - ry) % 3;
		a[py].par = px; //合併 
		a[py].rel = (ry + rel + rx) % 3;
		return true;
	}
	else {
		rx = (3 - rx) % 3;
		if(rel == (rx + ry) % 3) return true;
		return false;
	}
} 


int main() {
	int n, k;
	while(scanf("%d%d", &n, &k) == 2) {
		for(int i = 1; i <= n; ++i) { //初始化並查集 
			a[i].par = i;
			a[i].rel = 0;
		}
		int d, x, y, ans = 0;
		for(int i = 0; i < k; ++i) {
			scanf("%d%d%d", &d, &x, &y);
			if(x > n || y > n || (d == 2 && x == y)) {
				ans++;
				continue;
			}
			if(!unionset(x, y, d-1)) ans++;
		}
		printf("%d\n", ans);
		break; //只有一組資料 
	}
	return 0;
} 

如有不當之處歡迎指出!