帶權並查集(hdoj 3038,poj 1182,poj 2492)

阿偉學了三天的帶權並查集emmm,基本內容還算是掌握了
帶權並查集最重要的還是偏移量的理解,以及偏移量更新的方法,這裏主要應用了向量的知識。用例題來說明吧。

hdoj 3038 How Many Answers Are Wrong

題目大意: TT和FF在做一個遊戲(boring)。提供一些數的下標 i 和 j ,以即a[i],a[i+1],a[i+2]…,a[j]的和,判斷給出的是否是正確答案,如果判斷不了就當做正確答案。

題解: 此題要用到帶權並查集,也就是每個元素到根節點的距離,即偏移量。用dis表示。初始化爲0。偏移量的計算用 向量 比較方便。

當兩個點的根節點不相同時,需要將兩個節點進行合併,此時設a的根節點爲ra,b的根節點爲rb,a到b的距離爲輸入給出的c,因爲此時無法判斷所得的和是否正確,因此默認爲正確答案。進行兩個節點的合併,將rb作爲ra的根節點,則dis[ra]的計算方法如圖所示(向量的應用)
在這裏插入圖片描述
當兩個節點的根節點相同時,這時就可以判斷給出的值是否是正確答案了,因爲在尋找根節點的時候已經將每個節點都掛在根節點上,形式都如下圖所示,因此在計算所給的兩點之間的和時,只用dis[a]-dis[b]即可,具體如圖(向量)。
在這裏插入圖片描述
到這裏,偏移量就結束了,再看看如何尋找根節點。

在之前,我的查找根節點都是這樣寫的emmm

int find_root(int x)
{
    while(parent[x]!=x)
        x=parent[x];
    return x;
}

這麼寫只是從上到下查找根節點,並不會路徑壓縮,即就算6->7->8,查找到6的根節點是8,並不會把6直接掛到8上面,即不會進行路徑壓縮。
然後看了dalao的代碼

int find_root(int x)
{
    return parent[x]==x ? x : parent[x]=find_root(parent[x]);
}

WTF!!!(對8起,我爆粗口了)
這段代碼也就是這樣的:

int find_root(int x)
{
    if(parent[x]==x)
        return x;
    else
        return parent[x]=find_root(parent[x]);
}

在查找根節點的時候已經將它掛在根節點上了,也就是如圖的亞子~
在這裏插入圖片描述
Soga~~
從圖中也能看出來,在每次遞歸的時候都需要更新dis的值,即加上其父節點的dis值。。
因此find_root函數就可以這麼表示:

int find_root(int x)
{
    if(parent[x]!=x)
    {
        int tmp=parent[x];
        parent[x]=find_root(parent[x]);//遞歸查找根節點
        dis[x]+=dis[tmp];//更新dis
    }
    return parent[x];
}

最後貼上AC代碼:

#include <iostream>
using namespace std;
int n,m;
int parent[200010],dis[200010];
void init()
{
    for(int i=0;i<=n;i++)
    {
        parent[i]=i;
        dis[i]=0;
    }
}
int find_root(int x)
{
    if(parent[x]!=x)
    {
        int tmp=parent[x];
        parent[x]=find_root(parent[x]);//遞歸查找根節點
        dis[x]+=dis[tmp];//更新dis
    }
    return parent[x];
}
int main()
{
    while(cin>>n>>m)
    {
        init();
        int ans=0;
        for(int i=0;i<m;i++)
        {
            int a,b,c;
            cin>>a>>b>>c;
            a--;
            int aa=find_root(a);
            int bb=find_root(b);
            //判斷兩點的根節點是否相同,如果相同則
            //說明兩點都與根節點有關,可以直接判斷是否是錯誤答案
            //如果根節點不相同,則說明無法從已知條件判斷是否是錯誤答案
            //也就是沒必要判斷,將兩點合併即可
            if(aa==bb)
            {
                if(dis[a]-dis[b]!=c)
                    ans++;
            }
            else
            {
                parent[aa]=bb;
                dis[aa]=-dis[a]+c+dis[b];//向量計算
            }
        }
        cout<<ans<<endl;
    }
}

poj 1182 食物鏈
題目大意: 動物王國有一種食物鏈(反正我覺得很神奇),A喫B,B喫C,C喫A(環形),要求你判斷給出的關係是否正確,如果判斷不了,則默認是正確的。
題解: 此題是並查集必做的題,和上一題差不多,重點也是在偏移量這裏。參見dalao博客
當第二三中情況都排除的時候,來判斷第一種情況。

首先將偏移量設爲三種情況:
dis=0 表示x和父節點是同類
dis=1 表示x被父節點喫
dis=2 表示x喫父節點

在尋找根節點時,因爲在找到之後就會將其直接掛到根節點上,因此dis[x]也要更新(在回溯時)。假設x,y的關係爲r1,y,z的關係爲r2,則x,z的關係爲(r1+r2)%3 (具體原因請參見大佬博客)

當x和y的父節點相同時:(此時可以判斷給出的關係是否正確)
若d=1,即x和y是同類,只要判斷dis[x]和dis[y]是否相等即可。
若d=2,即x喫y,在所有表示“喫”的關係中,都遵循着一個規律:(dis[x]+1)%3=dis[y] ,若x,y滿足這個關係,則說明是正確的。

當x和y的父節點不相同時:(此時無法判斷,直接合並)
將x的根節點作爲y的根節點的根節點
若d=1,即x和y是同類,則y對x的關係爲0
若d=2,即x喫y,則y對x的關係爲1
綜上:無論d是1還是2,y對x的關係都爲d-1
則dis[ry]的求法就如下圖所示了(向量)
在這裏插入圖片描述
這個ry->y是3-dis[y],得到的關係正好是與y->ry相反
貼上AC代碼:

#include <iostream>
#include <stdio.h>
using namespace std;
int n,k;
int parent[50001],dis[50001];
//dis=0 x與父節點同類
//dis=1 x被父節點喫
//dis=2 x喫父節點
void init()
{
    for(int i=1;i<50001;i++)
    {
        parent[i]=i;
        dis[i]=0;
    }
}
int find_root(int x)
{
    if(parent[x]!=x)
    {
        int tmp=parent[x];
        parent[x]=find_root(parent[x]);
        dis[x]=(dis[x]+dis[tmp])%3;
    }
    return parent[x];
}
int main()
{
    cin>>n>>k;
    init();
    int ans=0;
    while(k--)
    {
        int x,y,d;
//        cin>>d>>x>>y;
        scanf("%d %d %d",&d,&x,&y);//這裏必須用scanf,用cin WA了好幾發
        if(x>n||y>n)//第二個判別條件
        {
            ans++;
            continue;
        }
        if(d==2&&x==y)//第三個判別條件
        {
            ans++;
            continue;
        }
        int xx=find_root(x);
        int yy=find_root(y);
        if(xx==yy)//如果根節點相同則進行判斷
        {
            if(d==1)
            {
                if(dis[x]!=dis[y])
                    ans++;
            }
            else
            {
                if((dis[x]+1)%3!=dis[y])
                    ans++;
            }
        }
        else//如果根節點不同則合併
        {
            parent[yy]=xx;
            dis[yy]=(3-dis[y]+d-1+dis[x])%3;
        }
    }
    cout<<ans<<endl;
}

poj 2492 A Bug’s Life
題目大意: 一種生物有雌雄之分,並且正確的交配方法是雌雄交配,一位教授想判斷他們交配方法是否正確(判斷是否是同性戀。。)
題解: 此題要用到帶權並查集(關於種類並查集和帶權並查集的區別,請點擊這裏
首次,設一個dis數組表示與根節點的關係。當值爲0時,表示同性,當值爲1時,表示異性。首先初始化爲0。
尋找父節點時,更新dis值:
在這裏插入圖片描述
找到規律:dis[x]=(dis[x]+dis[rx])%2

當兩個數據的根節點相同時,只要判斷他們的dis值是否相同就行,如果dis值相同說明他們和根節點的關係相同,則他們是同性。反之相反。

當兩個數據的根節點不同時,需要進行合併,合併時計算dis[rb]的值時,可以用向量,也可以找規律。如圖所示:
在這裏插入圖片描述
由於向量方向相反不會影響向量的值(兩個bug的關係無論從哪個bug看都是一樣的),則得到:
dis[rb]=(dis[a]+dis[b]+1)%2
AC代碼:

#include <iostream>
#include <cstdio>
using namespace std;
int n,m,flag=0;
int parent[2001],dis[2001];
void init()
{
    for(int i=0;i<2001;i++)
    {
        parent[i]=i;
        dis[i]=0;
    }
}
int find_root(int x)
{
    if(parent[x]!=x)
    {
        int tmp=parent[x];
        parent[x]=find_root(parent[x]);
        dis[x]=(dis[x]+dis[tmp])%2;
    }
    return parent[x];
}
int main()
{
    int k;
    cin>>k;
    for(int i=0;i<k;i++)
    {
        scanf("%d %d",&n,&m);
        flag=0;
        init();
        while(m--)
        {
            int a,b;
            scanf("%d %d",&a,&b);
            int aa=find_root(a);
            int bb=find_root(b);
            if(aa==bb)
            {
                if(dis[a]==dis[b])
                    flag=1;
            }
            else
            {
                dis[bb]=(dis[a]+dis[b]+1)%2;
                parent[bb]=aa;
            }
        }
        cout<<"Scenario #"<<i+1<<":"<<endl;
        if(flag)
            cout<<"Suspicious bugs found!"<<endl;
        else
            cout<<"No suspicious bugs found!"<<endl;
        cout<<endl;
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章