Find them, Catch them POJ - 1703 (关系型并查集)

传送门

题解:是一道关系型并查集,做着感觉不好,和传送门这道很像,目前了解了两种解法。

解法一:建立人物的反面人物,也就是如果说如果A和B是不同团队,那么认为(A+N)与B是同一团队,(B+N)与A是同一团队,跟据这样构造,最后看两个人是否是一个团队,只需要把条件稍微进行更改即可。

附上代码:

include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;

const int MAXN=1e5+5;

int f[MAXN*2];

int find(int u)
{
    if(u==f[u]){
        return u;
    }else{
        f[u]=find(f[u]);
        return f[u];
    }
}

void merge(int u,int v)
{
    int t1=find(u);
    int t2=find(v);
    if(t1!=t2){
        f[t2]=t1;
    }
    return ;
}

int main()
{
    int t;
    scanf("%d",&t);
    while(t--){
        int n,m;
        scanf("%d%d",&n,&m);
        for(int i=0;i<=2*n+5;i++){
            f[i]=i;
        }
        char p;
        int x,y;
        for(int i=1;i<=m;i++){
            scanf(" %c%d%d",&p,&x,&y);
            if(p=='A'){
                if(find(x)==find(y)||find(x+n)==find(y+n)){
                    printf("In the same gang.\n");
                }else if(find(x)==find(y+n)||find(x+n)==find(y)){
                    printf("In different gangs.\n");
                }else{
                    printf("Not sure yet.\n");
                }
            }else{
                merge(x,y+n);
                merge(x+n,y);
            }
        }
    }
    return 0;
}

解法二:

我们可以定义一个整型数组表示当前节点与父节点的关系,偶数代表同类,奇数代表异类。初始化时每一个节点的父节点都为自己,关系数都为0。

每次进行查找一个节点的根节点时,都应该进行路径压缩,将沿途节点的父节点都修改为根节点,而关系数也应该对应地修改为与根节点的关系。该节点与根节点的关系数为该节点到根节点沿途所有节点的关系数之和(不包括该节点与当前父节点的关系数)。

进行合并操作时我们知道需要合并的两个原节点的关系为异类,如果这两个原节点的根节点相同(也就是说在同一个集合里面),那么我们可以直接忽略合并操作;如果这两个原节点的根节点不相同,那么应该对这两个原节点的根节点进行合并,合并后应该满足这两个原节点的关系为异类,我们需要并且只需要更新拥有新根的原根节点信息即可
。合并方式为将其中一个根节点的父节点修改为另一个根节点,而该根节点的关系数则应修改为两个原节点与原根节点的关系数之和加1,因为已知的信息是两个原节点的关系为异类,这样的操作是根据两个原节点之间关系以及两个原节点与对应根节点之间的关系得来的。

附上代码:

#include <stdio.h>
#define N 100001

int bleg[N];    //存储父节点
int rela[N];    //存储与父节点的关系,偶数为同类,奇数异类
int t;
int n, m;
char *ns = "Not sure yet.";
char *dg = "In different gangs.";
char *sg = "In the same gang.";

void Init();        //初始化

int Find(int x);    //并查集查找

void Union(int x, int y);   //并查集合并

int main()
{
    char a;
    int x, y;
    scanf("%d", &t);
    while (t--)
    {
        scanf("%d %d", &n, &m);
        Init();           
        while (m--)
        {
            scanf(" %c %d %d", &a, &x, &y);
            if (a == 'D')
            {
                Union(x, y);
            }
            else
            {
                if (Find(x) != Find(y))     //如果两节点的根节点不相同,则无法知道两节点的关系
                {
                    puts(ns);
                }
                else if ((rela[x] + rela[y]) % 2 == 0)  //推论可得,表示两节点的关系数为两节点与共同的根结点的关系数之和
                {
                    puts(sg);
                }
                else
                {
                    puts(dg);
                }
            }
        }
    }
    return 0;
}

void Init()     //初始化
{
    int i;
    for (i=0; i<N; i++)
    {
        bleg[i] = i;
        rela[i] = 0;
    }
    return;
}

int Find(int x)         //并查集查找
{
    int y = bleg[x];
    int sum = rela[x];
    int temp, z;
    while (y != bleg[y])    //当y不是根节点时
    {
        sum += rela[y];
        y = bleg[y];
    }
    while (x != bleg[x])    //路径压缩并将沿途所有节点的关系改成与根节点的关系
    {
        z = bleg[x];
        temp = rela[x];
        rela[x] = sum;
        bleg[x] = y;
        sum -= temp;
        x = z;
    }
    return y;
}

void Union(int x, int y)    //并查集合并
{
    int fx = Find(x);
    int fy = Find(y);
    if (fx == fy) return;
    bleg[fx] = fy;
    rela[fx] = rela[x] + rela[y] + 1;
    return;
}

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值