並查集(poj 2524,poj 1611,poj 2236,poj 1308,hdoj 1272)

剛剛接觸並查集,還不是很熟練,先從板子題開始吧

poj 2524 Ubiquitous Religions
題目大意: 統計一羣人中加的宗教有最多有幾種。
題解: 將m組數據挨個讀入,進行合併,先尋找每個人的祖先,如果祖先不同,則進行合併。最後統計祖先是本身的個數即爲answer
AC代碼:

#include <iostream>
using namespace std;
int n,m,time=0;
int parent[50001];
int ranks[50001];//合併時如果兩棵樹的rank不同,則從rank小的向rank大的連邊
void initialize()//初始化parent數組
{
    for(int i=1;i<=n;i++)
    {
        parent[i]=i;
        ranks[i]=0;
    }
}
int find_root(int x)//查找祖先
{
    int x_root=x;
    while(parent[x_root]!=x_root)
    {
        x_root=parent[x_root];
    }
    return x_root;
}
void unite(int x,int y)//合併
{
    int x_root=find_root(x);
    int y_root=find_root(y);
    if(x_root!=y_root)
    {
        if(ranks[x_root]<ranks[y_root])
            parent[x_root]=y_root;
        else
            if(ranks[y_root]<ranks[x_root])
            parent[y_root]=x_root;
        else
        {
            parent[y_root]=x_root;
            ranks[x_root]++;
        }
    }
}
int main()
{
    while(cin>>n>>m&&n+m)
    {
        int ans=0;
        initialize();
        for(int i=0;i<m;i++)
        {
            int x,y;
            cin>>x>>y;
            unite(x,y);
        }
        time++;
        for(int i=1;i<=n;i++)
        {
            if(parent[i]==i)
                ans++;
        }
        cout<<"Case "<<time<<": "<<ans<<endl;
    }
}

poj 1611 The Suspects
題目大意: 0是病人,與0同組的其他人都是嫌疑人,並且其他與嫌疑人一個組的也是嫌疑人(一旦組中的成員是嫌疑人,該組中的所有成員都是嫌疑人 )。求共有多少嫌疑人。
題解: 此題用並查集將與0有接觸的人合併在一起,最後計算所有成員中與0同祖先的成員個數。
AC代碼:

#include <iostream>
using namespace std;
int n,m;
int parent[30001],ranks[30001];
void init()
{
    for(int i=0;i<=n;i++)
    {
        parent[i]=i;
        ranks[i]=0;
    }
}
int find_root(int x)
{
    while(parent[x]!=x)
    {
        x=parent[x];
    }
    return x;
}
void unite(int x,int y)
{
    x=find_root(x);
    y=find_root(y);
    if(x!=y)
    {
        if(ranks[x]>ranks[y])
            parent[y]=x;
        else
            if(ranks[x]<ranks[y])
            parent[x]=y;
        else
        {
            parent[y]=x;
            ranks[x]++;
        }
    }
}
int main()
{
    while(cin>>n>>m&&n+m)
    {
        init();
        for(int i=0;i<m;i++)
        {
            int t,a,b;
            cin>>t;
            for(int j=0;j<t;j++)
            {
                if(j==0)
                    cin>>a;
                else
                {
                    cin>>b;
                    unite(a,b);
                    a=b;
                }
            }
        }
        int root=find_root(0);
        int ans=0;
        for(int i=0;i<=n;i++)
        {
            if(find_root(i)==root)
                ans++;
        }
        cout<<ans<<endl;
    }
}

poj 2236 Wireless Network
題目大意: 地震將電腦網絡全都打破了,需要挨個連接起來,給出電腦數量和能夠支持的最長距離。並且有兩個操作,一個操作是修復電腦,另一個是檢驗兩個電腦是否可以連接在一起。也就是說,如果計算機A和計算機B可以直接通信,或者計算機C可以與A和A進行通信,則計算機A和計算機B可以進行通信。
題解: 此題用並查集寫,先排除兩個電腦距離大於規定距離的電腦,然後再維護並查集就可以了。
AC代碼:

#include <iostream>
#include <string.h>
using namespace std;
int x[1010],y[1010];
int vis[1010],parent[1010],ranks[1010];
int n,d;
bool check(int a,int b)
{
    if((x[a]-x[b])*(x[a]-x[b])+(y[a]-y[b])*(y[a]-y[b])>d*d)
        return false;
    return true;
}
void init()
{
    for(int i=1;i<=n;i++)
    {
        parent[i]=i;
        ranks[i]=0;
    }
}
int find_root(int x)
{
    while(parent[x]!=x)
        x=parent[x];
    return x;
}
void unite(int a,int b)
{
    a=find_root(a);
    b=find_root(b);
    if(a!=b)
    {
        if(ranks[a]<ranks[b])
            parent[a]=b;
        else
            if(ranks[a]>ranks[b])
            parent[b]=a;
        else
        {
            parent[b]=a;
            ranks[a]++;
        }
    }
}
int main()
{
    cin>>n>>d;
    memset(vis,0,sizeof(vis));
    init();
    for(int i=1;i<=n;i++)
        cin>>x[i]>>y[i];
    char tmp;
    while(cin>>tmp)
    {
        if(tmp=='O')
        {
            int a;
            cin>>a;
            vis[a]=1;
            for(int i=1;i<=n;i++)
            {
                if(i==a||!vis[i])//如果還沒有被修復就不用合併了
                    continue;
                if(!check(i,a))//檢查是否符合距離的最值
                    continue;
                unite(i,a);
            }
        }
        else
        {
            int a,b;
            cin>>a>>b;
            if(find_root(a)==find_root(b))
                cout<<"SUCCESS"<<endl;
            else
                cout<<"FAIL"<<endl;
        }
    }
}

poj 1308 Is It A Tree?
題目大意: 判斷所給出的圖是否是樹。樹的條件爲:只有一個根節點,每個節點的入度不超過1。
題解: 總的來說,就是判斷一個圖中是否有環。這也正是並查集的作用之一。
AC代碼:

#include <iostream>
#include <set>
using namespace std;
typedef long long int ll;
ll parent[100001];
int flag=0;
void init()
{
    for(int i=0;i<100001;i++)
        parent[i]=i;
}
int find_root(int x)
{
    return parent[x]==x ? x : parent[x]=find_root(parent[x]);
}
void unite(int x,int y)
{
    x=find_root(x);
    y=find_root(y);
    if(x!=y)
        parent[y]=x;
    else
        flag=1;//如果兩個點的根節點相同,則說明有環
}
int main()
{
    ll a,b,k=0;
    while(1)
    {
        flag=0;
        k++;
        init();
        set<ll>st;
        cin>>a>>b;
        if(a==-1&&b==-1)
            return 0;
        if(a==0&&b==0)
        {
            cout<<"Case "<<k<<" is a tree."<<endl;
            continue;
        }
        if(a==b)
            flag=1;
        st.insert(a);
        st.insert(b);
        parent[b]=a;
        while(cin>>a>>b&&a+b)
        {
            unite(a,b);
            if(a==b)//如果兩個點相等也不符合要求,自己不能指向自己
                flag=1;
            st.insert(a);
            st.insert(b);
        }
        set<ll>::iterator it=st.begin();
        int tmp=find_root(*it);
        it++;
        for(;it!=st.end();it++)
        {
            if(find_root(*it)!=tmp)
            {
                flag=1;//如果有多個根節點也不符合要求
                break;
            }
        }
        if(flag)
            cout<<"Case "<<k<<" is not a tree."<<endl;
        else
            cout<<"Case "<<k<<" is a tree."<<endl;
    }
}

hdoj 1272 小希的迷宮
題目大意: 判斷一個圖是不是樹
題解: 並查集判環。即當把每組數據合併到樹上時,兩個數的根節點不能相等,若相等則說明圖上有環,即從一個點到另一個點有兩條及以上的路徑,不符合要求。當合並數據時,發現兩個數據的根節點相等,則標記 flag=1(即不符合要求)具體做法同上題相似,只是這個題要注意,當輸入的兩個數據相等時,也是符合要求的,但上一題就不符合emm
AC代碼:

#include <iostream>
#include <set>
using namespace std;
typedef long long int ll;
int flag=0;
ll parent[100010];
void init()
{
    for(int i=0;i<100010;i++)
        parent[i]=i;
}
int find_root(int x)
{
    return parent[x]==x ? x : parent[x]=find_root(parent[x]);
}
void unite(int x,int y)
{
    x=find_root(x);
    y=find_root(y);
    if(x==y) //若根節點相同,則有環,不符合要求,標記flag=1
        flag=1;
    else
        parent[y]=x;
}
int main()
{
    ll a,b;
    while(1)
    {
        flag=0;
        init();
        set<ll>st;
        cin>>a>>b;
        if(a==0&&b==0)
        {
            cout<<"Yes"<<endl;
            continue;
        }
        if(a==-1)
            break;
//        if(a==b)  這裏不用判斷輸入的兩個數是否相等
//            flag=1;
        st.insert(a);
        st.insert(b);
        parent[b]=a; //第一對數據也別忘了合併!!!
        while(cin>>a>>b&&a+b)
        {
//            if(a==b)
//                flag=1;
            unite(a,b);
            st.insert(a);
            st.insert(b);
        }
        set<ll>::iterator it=st.begin();
        int tmp=find_root(*it); //這裏一定要寫find_root,不能寫成parent[]!!!
        it++;
        for(;it!=st.end();it++)
        {
            if(find_root(*it)!=tmp) //還有這裏
            {
                flag=1;
                break;
            }
        }
        if(flag)
            cout<<"No"<<endl;
        else
            cout<<"Yes"<<endl;
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章