食物鏈

【問題描述】

動物王國中有三類動物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),輸出假話的總數。 

輸入文件(eat.in)

第一行是兩個整數N和K,以一個空格分隔。

以下K行每行是三個正整數 D,X,Y,兩數之間用一個空格隔開,其中D表示說法的種類。

  若D=1,則表示X和Y是同類。

  若D=2,則表示X吃Y。 

輸出文件(eat.out)

只有一個整數,表示假話的數目。

輸入樣例

輸入文件

對7句話的分析

100 7

 

1 101 1  

假話

2 1 2     

真話

2 2 3    

真話

2 3 3    

假話

1 1 3    

假話

2 3 1    

真話

1 5 5    

真話

 輸出樣例

3

數據和題解 密碼:a5tk

【問題分析】

食物鏈應該是並查集的開山之作,雖然經過廣大的oier’s的努力,難度已經從國際級變成了基礎級,但是對新手來說此題的處理和理解上還是有很多難點,那爲了節約大家的時間,我就總結下大家對此題的理解,做一個史上最詳盡的食物鏈的題解,保證讓讓傻瓜也能看懂。

【權值(relation)的確定】

我們根據題意,森林中有3種動物。A吃B,B吃C,C吃A。我們還要使用並查集,那麼,我們就以動物之間的關係來作爲並查集每個節點的權值。
       注意,我們不知道所給的動物(題目說了,輸入只給編號)所屬的種類。所以,我們可以用動物之間“相對”的關係來確定一個並查集。
  0 - 這個節點與它的父節點是同類
  1 - 這個節點被它的父節點吃
  2 - 這個節點吃它的父節點。
       注意,這個0,1,2所代表的意義不是隨便制定的,我們看題目中的要求。說話的時候,第一個數字(下文中,設爲d)指定了後面兩種動物的關係:
  1 - X與Y同類
  2 - X吃Y
    我們注意到,當 d = 1的時候,( d - 1 ) = 0,也就是我們制定的意義,當 d = 2的時候,( d - 1 ) = 1,代表Y被X吃,也是我們指定的意義。所以,這個0,1,2不是隨便選的

【 路徑壓縮,以及節點間關係確定】

確定了權值之後,我們要確定有關的操作。我們把所有的動物全初始化 relation[i]=0,f[i]=i
(1)路徑壓縮時的節點算法,路徑壓縮的關鍵是把路徑上的節點都指向根節點後,那相對關係也發生了變化,那如何修改節點和根節點的關係呢通過窮舉我們可以發現,當前節點與祖父節點的關係可以得出如下公式。
relation[now]=(relation[now]+relation[f[now]]) % 3
這個路徑壓縮算法是正確的
關於這個路徑壓縮算法,還有一點需要注意的地方,我們一會再談注意,根據當前節點的relation和當前節點父節點的relation推出當前節點與其父節點的父節點的relation這個公式十分重要!!它推不出來下面都理解不了!!自己用窮舉法推一下:
好吧,爲了方便伸手黨,我給出窮舉過程

          

       當x,y不在一個集合時就需要合併操作,合併時除了直接把x,y的根root(x)和root(y)並起來外,還需要重新計算root(x)和root(y)之間的關係,如果是把root(y)掛在root(x)上,那麼可以推出公式:
      relation[root(y)]=(3-relation[y]+(d-1)+relation[x]) % 3;
這個公式,是分三部分,這麼推出來的:
( d - 1 ) :這是X和Y之間的relation,X是Y的父節點時,Y的relation就是這個
3 - relation[y] = 根據Y與根節點的關係,逆推根節點與Y的關係       
這部分也是窮舉法推出來的,我們舉例:
    0(父子同類)( 3 - 0 ) % 3 = 0
   1(父吃子) ( 3 - 1 ) % 3 = 2 //父吃子
   2(子吃父) ( 3 - 2 ) % 3 = 1 //子吃父,一樣的哦親
注意,這個當所有集合都是初始化狀態的時候也適用


【判斷】

先處理特殊情況:
       1.當x>n或y>n時,爲假話
  2.當d=2而x=y時,爲假話
(1)首先,如何判斷1 X Y是不是假話。//此時 d = 1
   if ( X 和 Y 不在同一集合) Union(x,y,xroot,yroot,d)   
   else if x.relation != y.relation ->假話
(2)其次,如何判斷2 X Y是不是假話 //此時d = 2
   if ( X 和 Y 不在同一集合)Union(x,y,xroot,yroot,d)
   else (relation [y]+ 3 - relation[x] ) % 3 != 1 ->假話
  這個公式是這麼來的:
  3 - relation[x]得到了根節點關於x的relation,
  relation [y]+ 3 - relation[x]得到了y關於x的relation,所以,只要y關於x的relation不是1,就是y不被x吃的話,這句話肯定是假話!
    綜合(1) 和(2),無論d=1或2,只要滿足 ((relation[y]-relation[x]+3) % 3)<>(d-1) 即爲假話,3要加上,不然可能出現負數。

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
int relation[50010],f[50010];
int n,k,sum=0;
void init();
void merge(int,int,int,int,int);
int find(int);
void work();
int main()
{
	init();
	work();
	return 0;
}
void init()
{
	memset(relation,0,sizeof(relation));
	cin>>n>>k;
	for(int i=1;i<=n;i++) f[i]=i;	
}
void work()
{
	int d,x,y;
	for(int i=1;i<=k;i++)
	{
		cin>>d>>x>>y;
		if(x>n||y>n)
		{
			sum++;
			continue;
		}
		if(n==2 && x==y)
		{
			sum++;
			continue;
		}
		int fx,fy;
		fx=find(x);
		fy=find(y);
		if(fx!=fy) merge(fx,fy,x,y,d);
		else
		if((relation[y]+3-relation[x])%3!=(d-1)) sum++;
	}
	cout<<sum<<endl;
}
int find(int x)
{
	if(f[x]==x) return x;
	int fx;
	fx=find(f[x]);//不能直接f[x]=find(f[x]),更新f[x]後就無法計算x和根的關係了;	
	relation[x]=(relation[x]+relation[f[x]])%3;
	f[x]=fx;//路徑壓縮
	return f[x]; 
}
void merge(int fx,int fy,int x,int y,int d)
{
	f[fy]=fx;
	relation[fy]=(3-relation[y]+d-1+relation[x])%3;
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章