zoukankan      html  css  js  c++  java
  • 并查集

    并查集

     一、并查集维护相同集合

    E - How Many Tables

    Today is Ignatius' birthday. He invites a lot of friends. Now it's dinner time. Ignatius wants to know how many tables he needs at least. You have to notice that not all the friends know each other, and all the friends do not want to stay with strangers. 

    One important rule for this problem is that if I tell you A knows B, and B knows C, that means A, B, C know each other, so they can stay in one table. 

    For example: If I tell you A knows B, B knows C, and D knows E, so A, B, C can stay in one table, and D, E have to stay in the other one. So Ignatius needs 2 tables at least. 

    InputThe input starts with an integer T(1<=T<=25) which indicate the number of test cases. Then T test cases follow. Each test case starts with two integers N and M(1<=N,M<=1000). N indicates the number of friends, the friends are marked from 1 to N. Then M lines follow. Each line consists of two integers A and B(A!=B), that means friend A and friend B know each other. There will be a blank line between two cases.
    OutputFor each test case, just output how many tables Ignatius needs at least. Do NOT print any blanks. 
    Sample Input

    2
    5 3
    1 2
    2 3
    4 5
    
    5 1
    2 5

    Sample Output

    2
    4
    #include <iostream>
    #include <set>
    #include <cstring>
    //垂涎并查集很久了,终于开敲了yep;
    using namespace std;
    const int maxn=1005;
    int n,m;
    int per[maxn],ran[maxn];
    void init()             //初始化所有节点的父亲结点为其本身;
    {
        for(int i=1;i<=n;i++){
            per[i]=i;
            ran[i]=0;
        }
    }
    int fin(int x)
    {
        if(x==per[x])       //由子节点一直往上,查找到其根结点返回;
            return x;
        else
            return per[x]=fin(per[x]);
    }
    void unint(int x,int y)//合并两结点所在的集合;
    {
        x=fin(x);
        y=fin(y);
        if(x==y)
            return;
        if(ran[x]<=ran[y])  //以x为根的树高度小于等于y,将x合并到y上;
        {
            per[x]=y;
            ran[y]++;
        }else
            per[y]=x;
    }
    int main()
    {
        int t;
        cin >> t;
        while(t--)
        {
            int a,b;
            cin >> n >> m;
            init();
            for(int i=0;i<m;i++)
            {
                cin >> a >> b;
                unint(a,b);
            }
            //查有多少个根结点;
            set<int> thy;           //利用set去重,存下不同的根节点
            for(int i=1;i<=n;i++)
            {
                a=fin(i);
                thy.insert(a);
            }
            cout << thy.size() << endl;
        }
        return 0;
    }

    二、并查集维护不同的集合关系

    例:POJ1703

    Find them, Catch them

         

    Description

    The police office in Tadu City decides to say ends to the chaos, as launch actions to root up the TWO gangs in the city, Gang Dragon and Gang Snake. However, the police first needs to identify which gang a criminal belongs to. The present question is, given two criminals; do they belong to a same clan? You must give your judgment based on incomplete information. (Since the gangsters are always acting secretly.) 

    Assume N (N <= 10^5) criminals are currently in Tadu City, numbered from 1 to N. And of course, at least one of them belongs to Gang Dragon, and the same for Gang Snake. You will be given M (M <= 10^5) messages in sequence, which are in the following two kinds: 

    1. D [a] [b] 
    where [a] and [b] are the numbers of two criminals, and they belong to different gangs. 

    2. A [a] [b] 
    where [a] and [b] are the numbers of two criminals. This requires you to decide whether a and b belong to a same gang. 

    Input

    The first line of the input contains a single integer T (1 <= T <= 20), the number of test cases. Then T cases follow. Each test case begins with a line with two integers N and M, followed by M lines each containing one message as described above.

    Output

    For each message "A [a] [b]" in each case, your program should give the judgment based on the information got before. The answers might be one of "In the same gang.", "In different gangs." and "Not sure yet."

    Sample Input

    1
    5 5
    A 1 2
    D 1 2
    A 1 2
    D 2 4
    A 1 4
    

    Sample Output

    Not sure yet.
    In different gangs.
    In the same gang.
    

    Source

    #include <iostream>
    #include <cstdio>
    //并查集维护不同集合之间的关系;
    using namespace std;
    const int maxn=100005;
    int n,m;
    int per[maxn*2],ran[maxn*2];
    void init()
    {
        for(int i=1;i<=2*n;i++) //一开始这里没初始化到2*n,就出现了问题;
        {
            per[i]=i;
            ran[i]=0;
        }
    }
    int fin(int x)
    {
        return per[x]==x ? x : per[x]=fin(per[x]);
    }
    void unit(int x,int y)
    {
        x=fin(x);
        y=fin(y);
        if(x==y)
            return ;
        if(ran[x]==ran[y]){
            per[x]=y;
            ran[x]++;
        }else if(ran[x]>ran[y]){
            ran[y]=x;
        }else
            ran[x]=y;
    }
    bool same(int x,int y)
    {
        return fin(x)==fin(y);
    }
    int main()
    {
        //ios::sync_with_stdio(false);
        int t,a,b;
        scanf("%d",&t);//关了流同步还是TLE了,改成了scanf才过了!!!;
        while(t--){
            scanf("%d%d",&n,&m);
            init();
            char key;
            while(m--)
            {
                scanf("
    %c%d%d",&key,&a,&b);
                if(key=='D')
                {
                    unit(a,b+n);
                    unit(a+n,b);
                    continue;
                }
                if(same(a,b+n)||same(a+n,b))
                    printf("In different gangs.
    ");
                else if(same(a,b)||same(a+n,b+n))
                    printf("In the same gang.
    ");
                else
                    printf("Not sure yet.
    ");
            }
        }
        return 0;
    }
    //并查集维护不同集合之间的关系,
    //合并集合内的元素表明,这两集合属于不同元素;
    //合并,保留所有的可能性;

     另一种路径压缩的方法:

    int find(int x){
        int p = x, t;
        while(uset[p] != p)
            p = uset[p];
        while(x != p){
            t = uset[x];
            uset[x] = p;
            x = t;
        }
        return x;
    }

     例:白书POJ1182食物链  http://poj.org/problem?id=1182

    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

    题目思路:并查集维护不同的关系;

    #include <iostream>
    #include <cstdio>
    
    using namespace std;
    const int maxn=50005;
    int par[maxn*3];
    int n,k;
    void init()
    {
        for(int i=1;i<=n*3;i++)
            par[i]=i;
    }
    int fin(int x)
    {
        int p=x,t;
        while(par[p]!=p)
            p=par[p];   //  找到根结点;
        while(x!=p){
            t=par[x];
            par[x]=p;
            x=t;
        }
        return p;
    }
    void unit(int x,int y)
    {
        x=fin(x),y=fin(y);
        if(x!=y){
            par[x]=y;
        }
    }
    bool same(int x,int y)
    {
        return fin(x)==fin(y);
    }
    int main()
    {
        scanf("%d%d",&n,&k);
        int d,a,b,ans=0;
        init();
        for(int i=0;i<k;i++)
        {
            scanf("%d%d%d",&d,&a,&b);
            if(a<1||b<1||a>n||b>n||(d==2&&a==b)){
                ans++;
                continue;
            }
            if(d==1)
            {
                if(same(a,b+n)||same(a,b+2*n))
                    ans++;
                else{
                    unit(a,b);
                    unit(a+n,b+n);
                    unit(a+2*n,b+2*n);
                }
            }else
            {
                if(same(a,b)||same(a,b+2*n))
                    ans++;
                else{
                    unit(a,b+n);
                    unit(a+n,b+2*n);
                    unit(a+2*n,b);
                }
            }
        }
        printf("%d
    ",ans);
        return 0;
    }
  • 相关阅读:
    wpf passwordbox控件 光标移到最后
    C#程序 给IE网页IFRAME控件中所嵌入网页的元素赋值
    C#客户端填充外部IE浏览器中网页文本(input)且不提交
    C# 获取当前网页HTML
    WPF 带有提示文本的透明文本框
    C# 导出Excel文件 所导出文件打开时提示“Excel文件格式与扩展名指定格式不一致”
    php生成验证码
    Python命名规范
    UE4碰撞规则详解
    四大编程思想简述
  • 原文地址:https://www.cnblogs.com/Cloud-king/p/8582345.html
Copyright © 2011-2022 走看看