食物鏈(經典種類並查集問題)---詳解

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

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

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

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

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

輸出格式
只有一個整數,表示假話的數目。

數據範圍
1≤N≤50000,
0≤K≤100000
輸入樣例:
100 7
1 101 1
2 1 2
2 2 3
2 3 3
1 1 3
2 3 1
1 5 5
輸出樣例:
3

去年就做過這個很經典的題,但那時候沒有真正理解,現在再來整理吧~

思路:我們要判斷每次給出的信息是否是假話必須判斷他們之間的關係,但是存儲兩兩之間的關係顯然是不現實的,那麼我們可存儲每個數與根節點的關係。


即我們可以把這些關係看成一棵樹,距離根節點爲1的就是可以喫根節點的,爲2的就是可以可以喫第1層的,爲3的和根節點是同類(3可以喫2, 1->2 , 2-> 3 那麼一定存在 3->1,所以爲3的一定是被1喫的,而根節點也是被1喫的,那麼根節點和3是同類),那麼以此類推,我們可以發現,以3爲循環,只要知道距離,我們就可以找出每個點與根節點的關係。


還有一個問題是:距離是什麼?每次我們發現有兩個動物x,y, 他們存在x->y (y可以喫x)的關係的時候,x和y就產生了一條距離爲1的邊。但是由於我們的樹太深了會導致超時,我們需要壓縮路徑,這時候就要處理好每個節點和根節點的距離。
首先,路徑壓縮的時候,我們正常的寫法是:

int find(int x)
{
	if(x==pre[x])
		return x;
	return pre[x]=find(pre[x]);
}

由於我們多存儲了一個變量,即與根節點之間的距離,所以我們在壓縮路徑的時候要把距離也更新一下。
在這裏插入圖片描述
假如我們壓縮路徑的時候是這樣的,那麼此時d[x]代表到他的父節點的距離,壓縮完以後父節點變成了根節點,那麼我們讓d[x]加上父節點到根節點的距離即可。

int find(int x)
{
    if(x!=pre[x])
    {
        int t=find(pre[x]);
        d[x]+=d[pre[x]];
        pre[x]=t;
    }
    return pre[x];
}

還有就是我們合併的時候,也要處理好距離,具體的看代碼註釋。

代碼:

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
const int N = 5e4+10;
int pre[N],d[N];
int find(int x)  //查找根節點+路徑壓縮
{
    if(x!=pre[x])
    {
        int t=find(pre[x]);
        d[x]+=d[pre[x]];
        pre[x]=t;
    }
    return pre[x];
}
int main()
{
    int n,m,ans=0;
    cin>>n>>m;
    for(int i=1;i<=n;i++) pre[i]=i;
    while(m--)
    {
        int t,x,y;
        cin>>t>>x>>y;
        int fx=find(x),fy=find(y);
        if(x>n || y>n) ans++;
        else if(t==1)  //x,y是同類
        {
        	/*如果在同一棵樹上,根據與根節點的距離,判斷是否說了假話,如果他們之間
        	的距離%30,說明是同類,如果不爲0,說明是假話*/
            if(fx==fy && (d[x]-d[y])%3) ans++;
            else if(fx!=fy)
            {
            	/*合併的時候,要確保d[x]+d[fx]=d[y],他們纔是同類,所以d[fx]的值
            	要更新爲d[y]-d[x]*/
                pre[fx]=fy;
                d[fx]=d[y]-d[x];
            }
        }
        else if(t==2)  //x喫y的關係
        {
        	/*如果在同一棵樹上,根據與根節點的距離,判斷是否說了假話,如果他們之間
        	的距離%31,說明是同類,如果不爲1,說明是假話*/
            if(fx==fy && (d[x]-d[y]-1)%3) ans++;
            else if(fx!=fy)
            {
            	/*合併的時候,要確保d[x]+d[fx]=d[y]+1,他們纔是同類(因爲x喫y,
            	所以x的距離要比y多1),所以d[fx]的值要更新爲d[y]+1-d[x]*/
                pre[fx]=fy;
                d[fx]=d[y]+1-d[x];
            }
        }
    }
    cout<<ans<<endl;
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章