并查集

  在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中。

  有n个元素,一开始每个元素都是一个集合,我们用p[x]表示x的父亲结点,当p[x]=x时是根结点,那么一开始p[i]=i(i=1...n)。如果想把两个集合合并,只需要把一个集合的根结点接到另一个集合的根结点下就行了(我们只关心他们是不是一个集合,而不关心顺序问题)。比如有两个元素a,b,想把它们所在的集合合并,那么用find函数找到根结点,x=find(a),y=find(b),如果x!=y,那么p[x]=y就完成合并了。

  当我们查找一个元素的集合的根结点时,find函数可以写成循环或递归

int find(int x){
while(p[x]!=x){
x=p[x];}
return x;}

int find(int x){
return p[x]==x?x:find(p[x]);}

  但是如果数据很多并且成为了链状,那就每次都要顺着链找一遍,很费时间,因此我们可以在递归找到根结点后顺便把根结点变成下面那些点的父亲,这样下次找的时候基本不费时间了。也就是改成
int find(int x){
return p[x]==x?x:p[x]=find(p[x]);}

下面是想了很久的一道题。。

F - Find them, Catch them
Time Limit:1000MS    Memory Limit:10000KB    64bit IO Format:%I64d & %I64u

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.

 

  这个题要判断两个人是不是一伙的,难就难在这。。把有关系的人合并到一个集合,但怎么知道他们是不是一伙的呢?刚开始老想着先固定2个组,再把人分组,但是这样不行。。解决方法是用flag[]数组,flag[x]=0表示x和他父亲是一伙的,flag[x]=1说明x和他父亲不是一伙,每次操作都先有

x=find(a);
y=find(b);
如果是D,那么找到根结点后有
flag[x]=(flag[a]+flag[b]+1)%2;
因为如果a跟他根结点一伙,b和他根节点一伙(虽然flag记录的是他和他父亲结点是不是一伙,但其实利用每次查找然后直接把根结点变成他父亲的特点,查找后flag的值就是他和根节点的关系了!这里很关键),a又和b不是一伙,也就是两个根节点不是一伙的,反之也一样。

flag巧妙的写在find里,

int find(int x){
    if(p[x]==x) return x;
    int t=p[x];
    p[x]=find(p[x]);
    flag[x]=(flag[t]+flag[x])%2;
利用递归的性质,执行p[x]=find[p[x]];后x已经接在根结点上了,所以要在接之前记下它的父结点,因为返回的时候是倒着的,执行完p[x]=find[p[x]];后flag[t]已经是它原来父结点和根结点的关系了,而这时
flag[x]=(flag[t]+flag[x])%2;
就是在计算它现在和根结点的关系(因为知道它原来父结点和它的关系和它原来父结点和根结点的关系)。

其实每次D之后只处理了根结点之间的关系,下面那些结点的处理是在下一次查找的时候。


代码:

#include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<queue>
    using namespace std;
    char s[10];
    int p[100010];
    short flag[100010];
    int find(int x){
    if(p[x]==x) return x;
    int t=p[x];
    p[x]=find(p[x]);
    flag[x]=(flag[t]+flag[x])%2;
    return p[x];
    }
    int main()
    {
        int T,N,M;
        scanf("%d",&T);
        while(T--){
                scanf("%d%d",&N,&M);
                int i;
                for(i=1;i<=N;i++) p[i]=i;
                memset(flag,0,sizeof(flag));
        while(M--){
            int a,b,x,y;
            scanf("%s%d%d",s,&a,&b);
                x=find(a);
                y=find(b);
            if(s[0]=='D')
                if(x!=y){
                p[x]=y;
                flag[x]=(flag[a]+flag[b]+1)%2;
            }
            if(s[0]=='A'){
                if(x!=y) printf("Not sure yet.\n");
                else if(flag[a]==flag[b]) printf("In the same gang.\n");
                else printf("In different gangs.\n");
            }
        }
        }
        return 0;}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值