1. 程式人生 > >POJ 1182 食物鏈 (並查集)

POJ 1182 食物鏈 (並查集)

食物鏈
Time Limit: 1000MS Memory Limit: 10000K
Total Submissions: 48713 Accepted: 14202

Description

動物王國中有三類動物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

經典的並查集  下面摘抄挑戰的解法

今天在《挑戰程式設計競賽》中看到對於這題一種獨特的寫法,巧妙應用了並查集,於是理解後摘抄下來分享一下。

由於N和K很大,所以必須高效地維護動物之間的關係,並快速判斷是否產生了矛盾。並查集是維護 “屬於同一組” 的資料結構,但是在本題中,並不只有屬於同一類的資訊,還有捕食關係的存在。因此需要開動腦筋維護這些關係。

對於每隻動物i建立3個元素i-A, i-B, i-C, 並用這3*N個元素建立並查集。這個並查集維護如下資訊:

① i-x 表示 “i屬於種類x”。

②並查集裡的每一個組表示組內所有元素代表的情況都同時發生或不發生。

例如,如果i-A和j-B在同一個組裡,就表示如果i屬於種類A那麼j一定屬於種類B,如果j屬於種類B那麼i一定屬於種類A。因此,對於每一條資訊,只需要按照下面進行操作就可以了。

1)第一種,x和y屬於同一種類———合併x-A和y-A、x-B和y-B、x-C和y-C。

2)第二種,x吃y—————————合併x-A和y-B、x-B和y-C、x-C和y-A。

不過在合併之前需要先判斷合併是否會產生矛盾。例如在第一種資訊的情況下,需要檢查比如x-A和y-B或者y-C是否在同一組等資訊。

AC程式碼如下:
//
//  POJ 1182 食物鏈
//
//  Created by TaoSama on 2015-03-16
//  Copyright (c) 2015 TaoSama. All rights reserved.
//
#include <algorithm>
#include <cctype>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iomanip>
#include <iostream>
#include <map>
#include <queue>
#include <string>
#include <set>
#include <vector>
#define CLR(x,y) memset(x, y, sizeof(x))

using namespace std;
const int INF = 0x3f3f3f3f;
const int MOD = 1e9 + 7;
const int N = 50000;

int n, k, par[3 * N + 5], rank[3 * N + 5];

void init(int n) {
	for(int i = 1; i <= n; ++i) {
		par[i] = i;
		rank[i] = 0;
	}
}
int find(int x) {
	if(par[x] == x) return x;
	return par[x] = find(par[x]);
}
void unite(int x, int y) {
	x = find(x); y = find(y);
	if(x == y) return;
	if(rank[x] < rank[y]) {
		par[x] = y;
	} else {
		par[y] = x;
		if(rank[x] == rank[y]) ++rank[x];
	}
}
bool same(int x, int y) {
	return find(x) == find(y);
}

int main() {
#ifdef LOCAL
	freopen("in.txt", "r", stdin);
//	freopen("out.txt","w",stdout);
#endif
	ios_base::sync_with_stdio(0);

	scanf("%d%d", &n, &k);
	init(3 * n);
	int ans = 0;
	for(int i = 1; i <= k; ++i) {
		int x, y, op; scanf("%d%d%d", &op, &x, &y);

		if(x < 1 || x > n || y < 1 || y > n) {
			++ans;
			continue;
		}

		if(op == 1) {
			if(same(x, y + n) || same(x, y + 2 * n)) ++ans;
			//A-B捕食 A-C反捕食
			else {
				unite(x, y);
				unite(x + n, y + n);
				unite(x + 2 * n, y + 2 * n);
			}
		} else {
			if(same(x, y) || same(x + n, y)) ++ans;
			//x,y同組 y吃x
			else {
				unite(x, y + n);
				unite(x + n, y + 2 * n);
				unite(x + 2 * n, y);
			}
		}
	}
	cout << ans << endl;
	return 0;
}

網上的另一種解法:
1.p[x]表示x根結點。r[x]表示p[x]與x關係。r[x]=0 表示p[x]與x同類;1表示p[x]吃x;2表示x吃p[x]。
2.怎樣劃分一個集合呢?
  注意,這裡不是根據x與p[x]是否是同類來劃分。而是根據“x與p[x]能否確定兩者之間關係”來劃分,若能確定x與p[x]關係,則它們同屬一個集合
3.怎樣判斷一句話是不是假話?
  假設已讀入D ,X ,Y ,先利用findset()函式得到X,Y所在集合代表元素fx,fy,若它們在同一集合(即fx==fy)則可以判斷這句話真偽:
        若 D==1 而 r[X]!=r[Y] 則此話為假.(D==1 表示X與Y為同類,而從r[X]!=r[Y]可以推出 X 與 Y 不同類.矛盾.)
        若 D==2 而 r[X]==r[Y](X與Y為同類)或者r[X]==(r[Y]+1)%3(Y吃X)則此話為假。
4.上個問題中r[X]==(r[Y]+1)%3這個式子怎樣推來?
      假設有Y吃X,那麼r[X]和r[Y]值是怎樣?
        我們來列舉一下: 
<span style="white-space: pre;">		</span>           r[X]=0&&r[Y]=2 
                           r[X]=1&&r[Y]=0
                           r[X]=2&&r[Y]=1
          稍微觀察一下就知道r[X]=(r[Y]+1)%3;
      事實上,對於上個問題有更一般判斷方法:
           若(r[Y]-r[X]+3)%3!=D-1 ,則此話為假.
5.其他注意事項:
       在Union(d,x,y)過程中若將S(fy)合併到S(fx)上,則相應r[fy]必須更新為fy相對於fx關係。怎樣得到更新關係式?
       r[fy]=(r[x]-r[y]+d+3)%3;
AC程式碼如下:
#include<cstdio>
const int N=50001;
int p[N],r[N],n;
int findset(int x)
{
	if(x!=p[x])
	{
		int fx=findset(p[x]);
		r[x]=(r[x]+r[p[x]])%3;
		p[x]=fx;
	}
	return p[x];
}
bool Union(int d,int x,int y)
{
	int fx=findset(x),fy=findset(y);
	if(fx==fy)
	{
		if((r[y]-r[x]+3)%3!=d)return 1;
		else return 0;
	}
	p[fy]=fx;
	r[fy]=(r[x]-r[y]+d+3)%3;
	return 0;
}
int main()
{
	int k,ans,i,d,x,y;
	scanf("%d%d",&n,&k);
	ans=0;
	for(i=1;i<=n;i++)p[i]=i,r[i]=0;
	while(k--)
	{
		scanf("%d%d%d",&d,&x,&y);
		if(x>n||y>n||(x==y&&d==2)){ans++;continue;}
		if(Union(d-1,x,y))ans++;
	}
	printf("%d\n",ans);
	return 0;
}