poj 食物鏈 數據結構 並查集

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

Source

Noi 01

解法一 用帶權值的並查集
本題就是需要我們判斷給定的兩點是否滿足某種關係。同一顆樹裏面的任意兩節點間的關係我們是想知道的。
1。。根據向量如果已知A與b的關係(共只有三種關係),b與c的關係,那定會推出A與c的關係,可以在紙上畫畫,更清楚。據此,就能求得每個節點到其根節點的關係。
2。。 對於現在不在同一顆樹中元素,合併前需知道兩個子樹的根節點的關係。這在後面的推倒
上面兩個問題是解本題的核心!!!

之前並查集是處理的一類的元素操作,而該題元素之間有關係(同類,喫,被喫),因此對於每個元素除了需要保存每個元素的父親節點,還需保存於父親節點的關係。
之前的樹裏面代表任意兩個數在一個集合,而此問題代表任意兩個數的關係的以確認。

在進行樹與樹間合併時,是合併已經確定關係的元素,如現有根節點A的子樹 、根節點B的子樹如知道節點A 與節點b的關係就可合併。根據我們保存的信息我們可以推出任意節點與其根節點的關係,具體細節見代碼註釋。

補充:我看別人都說向量解答,剛開始有點懵,然後發現有點像相對位置,可以相加(特殊的規則),因此本問題可以擴展多個不同種類的動物。


#include<stdio.h>
#include<math.h>
#include<stdlib.h>
#include<algorithm>
#include<string.h>
#include<vector>
using namespace std;
int fa[50010];
int r[50010];
int ct;
void init(int n){
    int i;
    for(i=1;i<=n;i++)
    {
        fa[i] = i;
        r[i] = 0;
    }
}
int find(int x)
{
    if(x != fa[x]){
        int tmp = fa[x];   //注意,x的父親節點經過遞歸會改變,因此需臨時保留。
        fa[x] = find(fa[x]);  
        r[x] = (r[tmp] + r[x])%3;  //這了首先明白默認規則 r【i】的值爲0,代表 i節點與根節點同類
                                                            // r【i】的值爲2,代表 i節點被根節點喫
                                                            //  r【i】的值爲1,代表 i節點喫根節點
                                                            //關係是通過列出所有情況所找出來的,情況不多,就十來種 (枚舉所有x取0,1,2,fa【x】(x父親)取0,1,2的情況)就能看出x與其祖父的關係)
                                                            //而並查集的UNION(合併) 是把能確定兩點關係的節點保存在一顆樹中
                                                            //如果我們知道任意兩點的關係,就能把他們放在同一顆樹中(因爲確定x--rx關係
                                                            //rx--ry關係 x--y關係)[可以在紙上畫畫更清晰]                                    
    }
    return fa[x];
}
bool Union(int d,int x,int y)
{
    int rx = find(x);
    int ry = find(y);
    //printf("x=%d %d %d %d  ry=%d %d %d %d\n",x,rx,r[x],fa[x],y,ry,r[y],fa[y]);
    if(rx == ry){      //已經知道x、y間的關係,直接判斷,去掉不合法
        if( d==1 && r[x] != r[y])
        return 1;
        if(d==2)
        {
            /*if( (r[x] == 0 &&r[y]!=2) || (r[x] == 1 &&r[y]!=0)||
            (r[x] == 2 &&r[y]!=1))
            return 1;*/
            if(r[x] == 2 && r[y] != 0) return 1;
            if(r[x] == 1 && r[y] != 2) return 1;
            if(r[x] == 0 && r[y] != 1) return 1;
        }
        return 0;
    }
    fa[rx] = ry;
    r[rx] = (d-1 + r[y] - r[x] +3)%3; // ( ((d-1) +r[y]) + (3-r[x]) )%3
    //把x所在的子樹連接到y的根節點上 (只需改x的根節點rx與y的根節點ry的關係)
     //  公式的推倒,是難點!!! 首先我們已知x與y的關係(就是d的值),x與x的根節點的關係(即爲r[])
     // 首先可以通過r【i】,r【fa】的值知道i與fa【fa【i】】的關係   這裏我們可以導出x與ry的關係(1)
     // 然後還需推倒一下:通過r【i】的值,導出父親對孩子的關係,    這裏我們可以導出rx與x的關係(2)
     //最後 在利用一下 1 的性質知道 rx與ry的關係  這裏我們可以導出rx與ry的關係
    return 0;
       
}
int main()
{
    int i,j,k,n,m,d,x,y,tmp;
    ct=0;
    scanf("%d%d",&n,&m);
    init(n);
    for(i=0;i<m;i++){
        scanf("%d%d%d",&d,&x,&y);
        if(d==2&&x==y || x>n || y>n)
        {
            //printf("i = %d \n",i);
            ct++;continue;    
        }
        tmp = Union(d,x,y);    
        //if(tmp){
            //printf("i = %d \n",i);
        //}
        ct += tmp;
    }
    printf("%d\n",ct);
    return 0;
}

法2
巧妙地解法,超出常規的思維。
把本題變種成一般的並查集。
同樣
本題就是需要我們判斷給定的兩點是否滿足某種關係。同一顆樹裏面的任意兩節點間的關係我們是想知道的。

該開始第一次看本題在《挑戰程序設計》中沒懂。。
此次瞭解了。
#include<stdio.h>
#include<math.h>
#include<stdlib.h>
#include<algorithm>
#include<string.h>
#include<vector>
using namespace std;
int fa[3*50010];   
int r[3*50010];
int find(int x){
	if(x != fa[x]){
		fa[x] = find(fa[x]);
	}
	return fa[x];
}

int same(int x,int y){
	if(find(x)  == find(y))
	return 1;
	return 0;
}
void merge(int x,int y){            //一棵樹裏面的元素a,b,則代表有通過a能找到與b的關係,或不能通過a找到b的關係,具體什麼關係還看a與b的差值(幾倍n)
                                    //小一倍數代表同類,小兩倍代表a可喫b,小三倍代表被b喫(此時的a是小於n)

 x=find(x);y=find(y);
	if(r[x] < r[y]){
		fa[x] = y;
	}
	else {
		if(r[x] == r[y]){
			r[x] ++ ;
		}
		fa[y] = x;
	}
} 
int main()
{
	int i,j,k,n,m;
	int d,x,y; 
	int count  = 0;
	scanf("%d%d",&n,&k);
	for(i=1;i<=3*n;i++) {  //把每個元素變成3個,設第一個爲x,則第二個爲x+n,第三個 x+2n
                    fa[i] = i;r[i] = 0;	
	}
	for(i=0;i<k;i++){		
		scanf("%d%d%d",&d,&x,&y);
		if(x>n || y>n){
			count++;
			continue;
		}
		if(d==1){
			if(same(x,y+n)||same(x,y+2*n)) //已知x y不是同一類 ,排除的情況
			{
				count++;
				continue;
			}
			merge(x,y);
			merge(x+n,y+n);
			merge(x+2*n,y+2*n);
		}	
		else {
			if(same(x,y)||same(x,y+2*n))  //已知x 不能喫 y,,排除的情況
			{
				count++;
				continue;
			}
			merge(x , y+n);
			merge(x+n , y+2*n);
			merge(x+2*n , y);
		}	
		
	}
	printf("%d\n",count);
	return 0;
}



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