并查集

简介:在一些有N个元素的集合应用问题中,通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找某个元素在哪个集合中。这类问题就需要用到并查集。
 
常用操作:
》初始化集合:每个节点开始的父节点都是本身
for(int i=1; i<=6; i++)
    father[i] = i;

》合并两个集合:将两个有关系的节点进行合并, 也就把这两个点所属的集合进行合并

void  memage(int a, int b) //合并节点(a,b)
{
    int a1 = find(a);      // 找到a的父节点
    int b1 = find(b);      // 找到b的父节点
    if(a1 != b1)           //如果a与b的父节点不相同,则把a,b所属的集合合并  
        father[a1] = b1;
}

》查询点:查找某个点的父节点,合并集合时需要用到~

int find(int x)
{
  if(x != father[x])  //节点i的父节点不是本身,则需要找到它所属集合中的祖先祖先指(所属集合中 节点的父节点是本身的节点)
    fathre[x] = find(father[x]); //这样在查找的同时会沿途修改(父节点不是其祖先节点的节点)让其父节点成为其祖先节点,方便下一次查找
  return father[x];   //返回父节点的值
} 

 

大意:有两个帮派,
      有两种操作:
      》D a b 代表 a b不属于同一个帮派
      》A a b 询问 a b什么关系(一个帮派,敌对帮派,不确定)
思路:
    D a b表示两个人肯定是有联系的,因为两个人是敌是友都要靠彼此的条件来制约,把这样有关系的并在一起,作为一个集合。
敌人的敌人是朋友 这句话很经典的
 
证明后的结论:
    在一个已经建好的一个集合里面,任意两个节点的关系由它们分别到根节点的距离决定,无论他们到根节点的距离是奇数还是偶数,只要是相同的(奇数与奇数~偶数与偶数)那么他们肯定是同一帮派,否则为不同帮派。
 
代码:
#include<iostream>
#include<cstdio>
using namespace std;
#define N 100005
int fa[N];
int mark[N];

void first(int n)
{
    for(int i=1; i<N; i++) //初始化并查集数组与标记数组
    {
        fa[i] = i;
        mark[i] = 0;   //0代表距离0
    }
}

int find(int x)
{
    if(fa[x] != x)
    {
        int temp = fa[x];
        fa[x] = find(fa[x]);
        mark[x] = (mark[x] + mark[temp]) % 2; //当前节点与根节点的关系的更新~这里temp就是根节点。
    }
    return fa[x];
}

void memage(int x, int y)
{
    int x1, y1;
    x1 = find(x);
    y1 = find(y);
    if(x1 != y1)
    {
        fa[x1] = y1;  //合并两个集合
        mark[x1] = (mark[x] + mark[y] + 1) % 2;//更新根节点改变的集合的关系(奇变偶,偶变奇)
    }
}

int main(){
    int t;
    scanf("%d", &t);
    while(t--){
        int i, n, m;
        scanf("%d%d", &n, &m);
        first(n);
        while(m--) {
            int a, b;
            char ch[2];
            scanf("%s%d%d", ch, &a, &b);
            if(ch[0] == 'D') memage(a, b);
            else {
                if(find(a) != find(b)) printf("Not sure yet.\n");//必须在判断关系的最前面,有些集合只是更新了根结点关系,还未更新子节点关系
                else if(mark[a] == mark[b])
                    printf("In the same gang.\n");
                else printf("In different gangs.\n");
            }
        }
    }
    return 0;
}

 

 
思路:输入有特殊要求。注意处理特殊数据
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;
#define M 100000
#define max(a, b) (a>b?a:b)
#define min(a, b) (a>b?b:a)

int f[M+1], flag; //flag记录是否有回路
int mark[M+1];     //记录迷宫的节点下标

void first()
{
    for(int i=1; i<=M; i++) //初始化数组
    {
        mark[i]=0;
        f[i]=i;
    }
}

int find(int x)
{
    if(x != f[x])
        f[x] = find(f[x]);
    return f[x];
}

void  memage(int a, int b)
{
    int a1 = find(a);
    int b1 = find(b);
    if(a1 != b1) f[a1] = b1;
    if(a1 == b1) flag = 1; //表示flag标记a b两点之间有多条路径
}

int main()
{
    int n, m;
    while(scanf("%d%d", &n, &m) != EOF)
    {
        if(m == -1 && n == -1) break;
        if(n == 0 && m == 0) //这里有坑,特殊数据0 0 时输出为yes~~~这坑该怎么发现啊!!!!!
        {
            printf("Yes\n");
            continue;
        }
        first();
        int maxx=-1, minn=M+1, i;
        flag=0;        //每组数据初始为0
        while(m || n)  //输入结束条件n m同时为0
        {
            maxx= max(maxx, max(n, m)); //求迷宫内节点的最大下标与最小下标
            minn= min(minn, min(n, m));
            mark[n]=1;
            mark[m]=1;
            memage(n, m);
            scanf("%d%d", &n, &m);
        }
        if(flag)  printf("No\n");
        else  //两点间没有多条路径,则检查是否全部节点记录
        {
            for(i=minn, flag=0; i<=maxx; i++)
                if(mark[i] && f[i]==i) flag++; //如果属于一个集合,只有一个节点的根节点是本身
            if(flag == 1)
                printf("Yes\n");
            else
                printf("No\n");
        }
    }
    return 0;
}

 

 
大意:有n串字符,两个字符串首尾字符相同,就可以相连,是不是能够将所有的字符串连成一串。。
 
思路:
  并查集检查是否能连成一串
  数组检查   有字符没用到或者连的一串有分支~~
 
#include <iostream>
#include <cstdio>
#include <cstring>

using namespace std;

int fa[30];  //并查集用
int f[30];   //记录各个字母个数
int mark[30]; //标记

int first() //初始化~~
{
    for(int i=0; i<=30; i++){
        fa[i]=i;
        mark[i]=0;
        f[i]=0;
    }
}
int find(int a){
    if(a != fa[a])
        fa[a] = find(fa[a]);
    return fa[a];
}
int main(){
    int t, i;
    scanf("%d", &t);
    while(t--){
        int n, a, b;
        scanf("%d", &n);
        while(n--){
            char s[1005];
            scanf("%s", s);
            a = s[0]-'a'; //26个字母用[0,25]表示
            b = s[strlen(s)-1]-'a';
            f[a]--;    //a为每串子符的头字母 给它减一
            f[b]++;    //b为每串子符的尾字母 给它加一  头尾持平
            mark[b] = 1; //标记
            mark[a] = 1;
            fa[a] = fa[b] = find(a); //归入集合
        }
        int in=0, out=0, other=0, flag=0;
        for(i=0; i<30; i++){
            if(mark[i] && fa[i] == i) flag ++; //集合个数
            if(mark[i] && f[i] ==-1) in++; //剩余头字母个数
            if(mark[i] && f[i] == 1) out++;//剩余尾字母个数
            if(mark[i] && (f[i] <-1 ||  f[i] > 1) ) other++; //一种字母剩余多个~~
        }
        if(flag == 1) //所有的头字母尾字母都在一个集合
        {
            if((in==1 && out==1 && other == 0)||(in==0&&out==0&&other==0))
                printf("Ordering is possible.\n");
            else  printf("The door cannot be opened.\n");
        }
        else printf("The door cannot be opened.\n");
    }
    return 0;
}

 

posted @ 2015-09-22 16:01  马晨  阅读(102)  评论(0)    收藏  举报