Find them, Catch them(并查集的变形)

本文介绍了一种使用并查集解决犯罪团伙归属问题的方法,包括两种并查集的实现方式:一种是将所有节点合并到一个并查集并通过数组记录与根节点的关系;另一种则是维护两个独立的并查集,并通过结构体保存对立面的根节点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

感觉这个题很神奇,刚开始以为自己看懂了,但是后来却发现自己没懂的地方好多,运用了很多巧妙的地方,两种很好的方法供参考(很好的并查集的变形)

1.他是两个并查集之间的变化,两种方法第一种,将所有的情况两个集合的所有及节点合并到一个并查集中,通过数组rel表示与根节点或者父节点是否相同的问题,第二种,一个集合一个并查集,通过结构体保存他的对立面的根节点,通过判断确定是否是一个集合

2.若是第一种方法,需要每次进行关系的判断,更新他的rel值

H - Find them, Catch them
Time Limit:1000MS     Memory Limit:10000KB     64bit IO Format:%lld & %llu

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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
#include <iostream>
#include <cstdio>
#include <cstring>
#define MAX 100050   //指令的最大量
using namespace std;

int fa[MAX],rel[MAX];//关于rel  rel=1表示与父节点不属于同一集合

//并查集主要内容
//初始化n个元素
void init(int n)
{
    for(int i=1;i<=n;i++)
    {
        fa[i]=i;
        rel[i]=0;
    }
}

//查询树的根
int find(int x)                //每次find后rel的值将变正确
{
    if(fa[x]==x)
    {
        return x;
    }
    else
    {
        int t=find(fa[x]);              //先要从子节点向根节点搜索,进入最里面的节点,寻找根节点
        rel[x]=(rel[fa[x]]+rel[x])%2;   //此处很重要,是通过根节点与父节点(爷与父)的关系和这个节点与父节点原来状态的关系(是否是同一集合的问题)求这个节点现在的状态,也就是与将要连到根节点的关系(异或关系)
        return fa[x]=t;                  //关系确定后再将路径压缩,子节点连入根节点(此时rel的值也缺定了)
    }           //顺序不可变,先更新rel值,再连接点根节点上
}

void unite(int x,int y)
{
    int fx=find(x);
    int fy=find(y);
    fa[fx]=fy;                      //直接结合只要是D指令,将必定不是同一集合,将树中的两个数分在不同集合
    if (rel[y]==0)                  //且fx的父节点不是y而是y的根节点
        rel[fx]=1-rel[x];           //如果fy将成为fx的父节点,如果他将通过该x与原来根节点的关系rel[x]和现在新建成的根节点y和x(此时属于不同集合)得到原来根节点的值,也就是与现在根节点fy的关系(同时,他正确的情况下可以通过find将所以的值rel正确)
    else rel[fx]=rel[x];             //此时 的情况属于x和y了不同集合,y和fy不同集合(fy也就是其根节点也是父节点)那么x和fy同一集合,根节点的rel一定为0,那么rel[X]与原根节点是否同一集合就是rel[fx]
}

//判断x,y是否属于同一集合
bool same(int x,int y)
{
    return find(x)==find(y);
}

int main()
{
    int N;
    int n,m,i,x,y;
    char c;
    scanf("%d",&N);
    while(N--)
    {
        scanf("%d %d",&n,&m);
        init(n);
        for(i=1;i<=m;i++)
        {
            getchar();
            scanf(" %c %d %d",&c,&x,&y);
            if(c=='A')
            {
                 if(same(x,y)!=1)    //这里面包含find函数,所以rel值正确,为后面判断做铺垫,做不在这个树内则无法判断
                 {
                     printf("Not sure yet.\n");
                 }
                 else if(rel[x]==rel[y])  //FAND后的这个表示与根节点的相同与否,,也就是是偶一个集合
                 {
                     printf("In the same gang.\n");
                 }
                 else
                    printf("In different gangs.\n");
            }
            else
                unite(x,y);
        }
    }
    return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <stdio.h>
using namespace std;
int m,n;
struct note
{
    int fu;
    int opp;
};
struct note f[100050];
void init()
{
    int i;
    for(i=1;i<=n;i++)
    {
        f[i].fu=i;
        f[i].opp=-1;
    }

    return;
}
int getf(int v)
{
    if(v==f[v].fu)
        return v;
    else
    {
        f[v].fu=getf(f[v].fu);
        return f[v].fu;
    }
}
int merge(int u,int v)
{
    int t1,t2;
    t1=getf(u);
    t2=getf(v);
    if(t1!=t2)
    {
       f[t2].fu=t1;
       return 1;
    }
    return 0;
}
int main()
{
   int t,k,i,u,v,tv,tu;
   char s[3];
   scanf("%d",&t);
   for(k=1;k<=t;k++)
   {
       scanf("%d%d",&n,&m);
       init();
       for(i=1;i<=m;i++)
       {
           scanf("%s",s);
           scanf("%d%d",&u,&v);
           if(s[0]=='A')
           {
               tu=getf(u);
               tv=getf(v);
               if(tu==tv)
                  printf("In the same gang.\n");
               else if(f[tu].opp==tv||f[tv].opp==tu)
                 printf("In different gangs.\n");
               else
                printf("Not sure yet.\n");
           }
           else if(s[0]=='D')
           {
               tu=getf(u);
               tv=getf(v);
               if(f[tu].opp!=-1)
                  merge(f[tu].opp,tv);
               if(f[tv].opp!=-1)
                  merge(f[tv].opp,tu);
               tu=getf(tu);   //需要更新新加入的合并的节点的根节点的值,才能使各个点的对应面的根节点值正确
               tv=getf(tv);
               f[tu].opp=tv;
               f[tv].opp=tu;
           }
       }

   }
   return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值