• 并查集练兵场


    并查集: 

    N 个不同的元素分布在若干个互不相交集合
    中,需要进行以下3个操作:
     1. 合并两个集合
     2. 查询一个元素在哪个集合
     3. 查询两个元素是否属于同一集合

     一般用下面两个模板: 

    int get_par(int a)
    {
        if(par[a]!=a) par[a] = get_par(par[a]);
        return par[a];
    } 
    /* 也可以表示成这种形式,两种形式与数组的初始化有关。 
    int get_par(int a)
    {
        if(par[a]==-1) return a;
        return par[a] = get_par(par[a]);
    }
    */
    int query(int a, int b)
    {
        return get_par(a)==get_par(b);
    }
    
    void merge(int a, int b)
    {
        par[get_par(a)]=get_par(b);
    }

    《1》简单模板题。

    http://poj.org/problem?id=1611

    #include<iostream>
    using namespace std;
    
    const int MAXN = 30000;
    int n, m, k;
    int parent[MAXN+10];
    int total[MAXN+10];
    
    int GetParent(int a)
    {
        if(parent[a]!=a)
        parent[a]=GetParent(parent[a]);
        return parent[a];
    } 
    
    void Merge(int a, int b)
    {
        int p1 = GetParent(a);
        int p2 = GetParent(b);
        if(p1==p2)
        return;
        total[p1]+=total[p2];
        parent[p2] = p1;
    }
    
    int main()
    {
        while(true)
        {
            scanf("%d%d", &n, &m);
            if(n==0&&m==0) break;
            for(int i=0; i<n; i++)
            parent[i] = i, total[i]=1;
            for(int i=0; i<m; ++i)
            {
                int h,s;
                scanf("%d", &k);
                scanf("%d", &h);
                for(int j=1; j<k; j++)
                {
                    scanf("%d", &s);
                    Merge(h, s);
                }
            }
                printf("%d
    ", total[GetParent(0)]);
        }
            return 0;
    }
    View Code

     《2》再来道裸题。

    http://poj.org/problem?id=2524

    #include<iostream>
    #include<cstring>
    using namespace std;
    
    const int MAXN = 50000 + 10;
    int Fa[MAXN];
    
    int find(int x)
    {
        if(Fa[x]==-1) return x;
        return Fa[x]=find(Fa[x]);
    }
    
    void Merge(int u, int v)
    {
        int t1 = find(u);
        int t2 = find(v);
        if(t1!=t2) Fa[t1] = t2;
    }
    
    int main()
    {
        //freopen( "in.txt", "r", stdin );
        //freopen( "out.txt", "w", stdout );
        int n, m, u, v;
        int kase = 0;
        while(scanf("%d%d", &n, &m)==2)
        {
            if(n==0&&m==0) break;
            kase++;
            memset(Fa, -1, sizeof(Fa));
            while(m--)
            {
                scanf("%d%d", &u, &v);
                Merge(u, v);
            }
            int ans = 0;
            for(int i=1; i<=n; i++)
            if(Fa[i]==-1) ans++;
            
            printf("Case %d: %d
    ", kase, ans);
        }
        return 0;
    }
    View Code

     《3》 这个题对小恪来说有点难, 但是收获不小哦!

    http://poj.org/problem?id=1988

    这道题很容易想到暴力的方法, 很不幸的是一定会超时。  嘿嘿!

    #include<iostream>
    #include<cstdio>
    using namespace std;
    
    const int MAXN = 30000+10;
    int parent[MAXN];
    int sum[MAXN]; //表示砖块 i 所在堆的砖块总数。
    int under[MAXN];//under[i]表示砖块 i 下面有多少砖块。
    
    int GetParent(int a)
    {
        if(parent[a]==a) return a;
        int t=GetParent(parent[a]);
        under[a]+=under[parent[a]];
        parent[a] = t;
        return parent[a];
    } 
    
    //把 b 所在的堆,叠放到 a 所在的堆。 
    void Merge(int a, int b)
    {
        int n;
        int pa = GetParent(a);
        int pb = GetParent(b);
        if(pa==pb) return;
        parent[pb] = pa;
        under[pb] = sum[pa];//under[pb]在赋值前一定是 0 ,
        //因为parent[pb] = pb, pb一定是最底下的。 
        sum[pa]+=sum[pb];
    }
    
    int main()
    {
        int p;
        for(int i=0; i<MAXN; i++)
        {
            sum[i] = 1;
            under[i] = 0;
            parent[i] = i;
        }
        scanf("%d", &p);
        for(int i=0; i<p; i++)
        {
            char s[20]; int a, b;
            scanf("%s", s);
            if(s[0] == 'M')
            {
                scanf("%d%d", &a, &b);
                Merge(b, a);
            }
            else
            {
                scanf("%d", &a);
                GetParent(a);
                printf("%d
    ", under[a]);
            }
        }
        return 0;
    }
    View Code

     《4》这个题可以用纯粹的并查集解, 但是如何判定合并条件有点虐心!

    http://acm.hdu.edu.cn/showproblem.php?pid=1198

    #include<cstdio>
    const int MAXN = 100 + 10;
    char map[MAXN][MAXN];
    int Fa[MAXN*MAXN];
    
    int find(int x)
    {
        if(Fa[x]==-1) return x;
        return Fa[x] = find(Fa[x]);
    }
    
    void Merge(int a, int b)
    {
        int t1 = find(a);
        int t2 = find(b);
        if(t1!=t2) Fa[t1] = t2;
    }
    
    int main()
    {
        int m, n;
        while(scanf("%d%d", &m, &n))
        {
            if(m<0||n<0) break;
            for(int i=0; i<m; i++)
            scanf("%s", &map[i]);
            for(int i=0; i<m*n; i++)
            Fa[i]= - 1;
            for(int i=0; i<m; i++)
            for(int j = 0; j<n; j++)
            {
                //判定条件有点虐心!   开口向: 上 下  左  右。 
                if(i>0&&(map[i][j]=='A'||map[i][j]=='B'||map[i][j]=='E'||map[i][j]=='G'||map[i][j]=='H'||map[i][j]=='J'||map[i][j]=='K'))
                if(map[i-1][j]=='C'||map[i-1][j]=='D'||map[i-1][j]=='E'||map[i-1][j]=='H'||map[i-1][j]=='I'||map[i-1][j]=='J'||map[i-1][j]=='K')
                Merge(i*n+j, (i-1)*n+j);
                if(j>0&&(map[i][j]=='A'||map[i][j]=='C'||map[i][j]=='F'||map[i][j]=='G'||map[i][j]=='H'||map[i][j]=='I'||map[i][j]=='K'))
                if(map[i][j-1]=='B'||map[i][j-1]=='D'||map[i][j-1]=='F'||map[i][j-1]=='G'||map[i][j-1]=='I'||map[i][j-1]=='J'||map[i][j-1]=='K')
                Merge(i*n+j,i*n+j-1);
            }
            int res = 0;
            for(int i=0; i<m*n; i++)
            if(Fa[i]==-1) res++;
            printf("%d
    ", res);
        }
        return 0;
    }
    View Code

     《5》赤裸裸的模板题

    http://acm.hdu.edu.cn/showproblem.php?pid=1232

    #include<cstdio>
    const int MAXN = 1000 + 10;
    int Fa[MAXN];
    
    int find(int x)
    {
        if(Fa[x]==-1) return x;
        return Fa[x] = find(Fa[x]);
    }
    
    void Merge(int a, int b)
    {
        int t1 = find(a);
        int t2 = find(b);
        if(t1!=t2) Fa[t1] = t2;
    }
    
    int main()
    {
        int n, m;
        while(scanf("%d%d", &n, &m)!=EOF, n)
        {
            for(int i=1; i<=n; i++)
            Fa[i] = -1;
            int a, b;
            while(m--)
            {
                scanf("%d%d", &a, &b);
                Merge(a, b);
            }
            int res = 0;
            for(int i=1; i<=n; i++)
            if(Fa[i]==-1) res++;
            printf("%d
    ", res-1);
        }
        return 0;
    }
    View Code

     《6》来道难的吧(欧拉回路+字典树+并查集)

    http://poj.org/problem?id=2513

    此题好坑! 本想巧妙地绕过字典序, 然而题目中并未告诉你一共有那几种颜色(感觉这一点好贱!), 结果此题机智的卡死了我这等弱渣,弱渣是何等的悲哀啊! 先贴一下我的wong代码(适合各种颜色首字母都不同的情况, 此题数据中应有首字母相同的情况)

    #include<cstdio>
    #include<cstring>
    #include<iostream>
    #include<string>
    #include<vector>
    using namespace std;
    
    const int maxn = 1000 + 5;
    
    int pa[256];
    int findset(int x) { return pa[x] != x ? pa[x] = findset(pa[x]) : x; } 
    
    int used[256], deg[256]; // 是否出现过;度数
    
    int main()
    {
        int flag = 0;
        freopen("in.txt", "r", stdin);
        freopen("out.txt", "w", stdout);
        char s1[105], s2[105];
        memset(used, 0, sizeof(used));
        memset(deg, 0, sizeof(deg));
        for(int ch = 'a'; ch <= 'z'; ch++) pa[ch] = ch; // 初始化并查集
    
        int cc = 26; // 连通块个数
    
        while(scanf("%s%s", s1, s2)!=EOF)
        {
            flag = 1;
            char c1 = s1[0], c2 = s2[0];
            deg[c1]++;
              deg[c2]--;
              used[c1] = used[c2] = 1;
              int s1 = findset(c1), s2 = findset(c2);
              if(s1 != s2) { pa[s1] = s2; cc--; }
        }
        vector<int> d;
        for(int ch = 'a'; ch <= 'z'; ch++) {
          if(!used[ch]) cc--; //没出现过的字母 
          else if(deg[ch] != 0) d.push_back(deg[ch]);
        }
        bool ok = false;
        if(cc == 1 && (d.empty() || (d.size() == 2 && (d[0] == 1 || d[0] == -1)))) ok = true;
        if(ok&&flag) printf("Possible
    ");
        else printf("Impossible
    ");
        return 0;
      }
      
    View Code

    下面是一巨巨的AC代码:

    #include<stdio.h>
    #include<string.h>
    #include<iostream>
    #include<iostream>
    #include<algorithm>
    using namespace std;
    const int MAXN=500010;
    int F[MAXN];
    const int MAX=26;
    int degree[MAXN];//度数
    int color;//颜色编号,从0开始,最后就是颜色总数
    int find(int x)
    {
        if(F[x]==-1)return x;
        return F[x]=find(F[x]);
    }
    void bing(int a,int b)
    {
        int t1=find(a);
        int t2=find(b);
        if(t1!=t2) F[t1]=t2;
    }
    typedef struct Trie_Node
    {
        bool isWord;
        struct Trie_Node *next[MAX];
        int id;
    }Trie;
    int insert(Trie *root,char *word)//返回颜色编号
    {
        Trie *p=root;
        int i=0;
        while(word[i]!='')
        {
            if(p->next[word[i]-'a']==NULL)
            {
                Trie *temp=new Trie;
                temp->isWord=false;
                for(int j=0;j<MAX;j++)
                  temp->next[j]=NULL;
                temp->isWord=false;
                temp->id=0;
                p->next[word[i]-'a']=temp;
            }
            p=p->next[word[i]-'a'];
            i++;
        }
        if(p->isWord)
        {
            return p->id;
        }
        else
        {
            p->isWord=true;
            p->id=color++;
            return p->id;
        }
    }
    void del(Trie *root)
    {
        for(int i=0;i<MAX;i++)
        {
            if(root->next[i]!=NULL)
             del(root->next[i]);
    
        }
        free(root);
    }
    int main()
    {
       // freopen("in.txt","r",stdin);
       // freopen("out.txt","w",stdout);
        char str1[20],str2[20];
        Trie *root=new Trie;
        for(int i=0;i<MAX;i++)
          root->next[i]=NULL;
        root->isWord=false;
        root->id=0;
        color=0;
        memset(F,-1,sizeof(F));
        memset(degree,0,sizeof(degree));
        while(scanf("%s%s",&str1,&str2)!=EOF)
        {
            int t1=insert(root,str1);
            int t2=insert(root,str2);
         //   printf("%d %d
    ",t1,t2);
            degree[t1]++;
            degree[t2]++;
            bing(t1,t2);
    
        }
    //***********************************************
    //这个是根据F数组等于-1来找根结点
        int cnt1=0,cnt2=0;
        for(int i=0;i<color;i++)
        {
            if(F[i]==-1)cnt1++;
            if(degree[i]%2==1)cnt2++;
            if(cnt1>1)break;
            if(cnt2>2)break;
        }
        //数据比较坑人,存在0根木棒的情况,此时cnt1==0
        if((cnt1==0||cnt1==1)&&(cnt2==0||cnt2==2))
          printf("Possible
    ");
        else printf("Impossible
    ");
        //del(root);//单组输入可以不释放空间,可以节省时间
        return 0;
    
    /*
        //********************************
        //这种是找寻根结点,多个根节点来判断是不是连通
        bool flag=true;
        int tt1=find(0);
        int cnt=0;//统计度数为奇数的结点个数
        for(int i=0;i<color;i++)
        {
            if(find(i)!=tt1)flag=false;//不连通也不是欧拉回路
            if(!flag)break;
            if(degree[i]%2==1) cnt++;
            if(cnt>2) flag=false;//度数为奇的结点个数>2,肯定不是欧拉回路了
        }
        if(cnt==1)flag=false;
        if(flag) printf("Possible
    ");
        else printf("Impossible
    ");
        del(root);//单组输入可以不释放空间,可以节省时间
        return 0;
        //******************************************
        */
    }
    View Code

    我还是   too   yang! 草滩小恪仍需恶补!!!

     《7》用并查集判断种类

    http://poj.org/problem?id=1703

    #include <stdio.h>
    #include <algorithm>
    #include <iostream>
    #include <string.h>
    using namespace std;
    
    const int MAXN=100010;
    int Fa[MAXN];
    int val[MAXN];
    int find(int x)
    {
        if(Fa[x]==-1)return x;
        int tmp=find(Fa[x]);
        val[x]=(val[x]+val[Fa[x]])%2;
        return Fa[x]=tmp;
    }
    void Merge(int x,int y)
    {
        int t1=find(x);
        int t2=find(y);
        if(t1!=t2)
        {
            Fa[t1]=t2;
            val[t1]=(val[y]-val[x]+1)%2;
        }
    }
    int main()
    {
        int T;
        char str[10];
        int u,v;
        int n,m;
        scanf("%d",&T);
        while(T--)
        {
            scanf("%d%d",&n,&m);
            memset(Fa,-1,sizeof(Fa));
            memset(val,0,sizeof(val));
            while(m--)
            {
                scanf("%s%d%d",&str,&u,&v);
                if(str[0]=='A')
                {
                    if(find(u)!=find(v))
                    {//题目说两个集团至少有一个人,所以N==2的时候单独考虑,但是不考虑这个也可以AC,估计没有这样的数据
                        if(n==2)printf("In different ganges.
    ");
                        else printf("Not sure yet.
    ");
                    }
                    else
                    {
                        if(val[u]!=val[v])printf("In different gangs.
    ");
                        else printf("In the same gang.
    ");
                    }
                }
                else
                {
                    Merge(u,v);
                }
            }
        }
        return 0;
    }
    View Code
  • 相关阅读:
    325日报
    323日报
    Http请求种类
    http1.1和1.0的区别
    Java EE常用名词
    USEA、HASA、ISA
    基本类型及其封装类型的 == 和 equals()
    AOP中的连接点(Joinpoint)、切点(Pointcut)、增强(Advice)、引介(Introduction)、织入(Weaving)、切面(Aspect)
    B树和B+树
    DNS的寻址过程
  • 原文地址:https://www.cnblogs.com/acm1314/p/4596579.html
Copyright © 2020-2023  润新知