并查集练兵场

并查集: 

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
原文地址:https://www.cnblogs.com/acm1314/p/4596579.html