1. 程式人生 > >1182 (帶權幷查集,運用向量的知識巧妙解決)

1182 (帶權幷查集,運用向量的知識巧妙解決)

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

題意:題目告訴有  3  種動物,互相吃與被吃,現在告訴你  m  句話,其中有真有假,叫你判斷假的個數  (  如果前面沒有與當前話衝突的,即認為其為真話  )。每句話開始都有三個數 D A B,當D = 1時,表示A 和B是同類,當D = 2時表示A 吃 B。 

分析:既然要求假話,肯定是說的話跟以前說的存在矛盾,比如A->B,B->A,這樣很明顯第二句話是假話了,跟第一句話衝突,那麼我們應該怎麼去查詢這種衝突呢,我們知道查詢兩個節點之間關係最快的方法就是並查集,只要把一些節點歸為一個集合,查詢起來是非常方便的,那麼這道題是不是也可以這麼做呢?答案是肯定的,不過我們除了要記錄每個節點的根節點以外,還要記錄與根節點的關係

而我們要歸為一個集合的條件是看看這個節點 存不存關係, 先規定生物之間的關係, 0 表示二者同類, 1 表示A吃B,2表示B吃A; 

說假話的條件:
1-   直接說假話,A或者B 的值大於動物的總數,(很明顯是存在這種可能的,AB的取值範圍大於動物總數),或者當D = 2, 時候 A == B,這也是一個睜眼說的假話,(這是同類相食,我們不允許這樣的事情發生,所以這也是假話)。
2 -  間接說假話,間接說假話就是跟以前說的話有衝突,我們先查詢A和B的根節點為 ra 和 rb 如果ra == rb(根節點相同,說明二者屬於同一個集合,說明前面的話中二者存在 上面的三種關係之一,只需判斷當前這句話,和前面話中的關係所說是否一樣,不一樣就是假話,一樣就是真話); 當ra 和 rb 不同時,說明 A和B 以前不存在關係,讓他們兩個所屬集合合併就行,讓他們兩個存在關係就行了;

看關係的推導是不是和和向量關係一樣

大家看看這個是不是特別像什麼東西??對了,就是向量,如果我們把這個轉換成向量求是不是也可以呢???這是一個美好的想法,我們可以驗證一下。
很明顯A->C = (A->B+B->C) % 3
這也恰好是符合向量的運算,那麼相同一棵樹的節點間的關係就解決了。

下面是路徑壓縮 和 合併;在這裡就是幷查集的知識了,運用自己到根節點的關係 進行路徑壓縮 和 合併,下面ra 和 rb 是a,b的根節點;

當給出 a和b已經存在關係了,也就是ra==rb ,他們在一個集合中,判斷當前給出的關係是不是 和 以前的推匯出的關係一樣;

只需判斷 a->b + b->rb 是不是等於 a->ra 就行了(ra==rb)若一樣就是真話,若不一樣就是假話;

當 a和b以前不存在關係時,也就是 ra!= rb;這時候需要合併,把根節點進行合併,根節點之間存在關係了,那麼就成一顆樹了,這棵樹中任意兩個節點都存在關係;

已知 a->b、 b->rb 、a->ra 之間的關係,求 ra ->rb的關係;

ra->rb = ra->b + b -> rb;

ra->b = a->b - a->ra

兩式合併

ra->rb = a->b - a->ra + b->rb;

程式碼:

#include<stdio.h>
//#include<bits/stdc++.h>
using namespace std;
#define Max  50005
int f[Max],r[Max]; //r[x] 存 x->f[x]的關係; 
int n,k;
void init()
{
	for(int i = 0;i<=n;i++)
		f[i] = i,r[i] = 0;
}

int find(int u)
{
	int k = f[u];
	if(f[u]==u)
		return u;
	else 
	{
		f[u] = find(f[u]);
		r[u] = (r[u] + r[k])%3;  //回溯時 u->root = u-> f[u] + f[u]->root;
		// 都是對根節點所說的,都是路徑壓縮; 
		return f[u];  
	}
}
int main()
{
	scanf("%d%d",&n,&k);
	init();
	int d,x,y;
	int cnt = 0;
	
	// 當根節點相同時,說明現在的x,y 存在上述關係0,1,2 中的一種關係,只需看看這句話和
	// 當前所存在的關係相不相符;不相符就是假話;相符就是真話; 
	//  當根節點不相同時,說明現在的x,y還沒有關係,需要合併關係; 
	while(k --)
	{
		scanf("%d%d%d",&d,&x,&y);
		int rx = find(x),ry = find(y);
		if(x>n||y>n||(d==2&&x==y))
			cnt ++;
		else if(rx==ry&&((d-1)+r[y])%3!=r[x])   
			cnt ++;
		else if(rx!=ry)
		{
			f[rx] = ry;
			r[rx] = ((d-1)-r[x]+r[y]+3)%3;   // 中間有減號,可能為負,所以先加上3,再對3取餘 
		}	
	}
	printf("%d\n",cnt);
	return 0;
}