不同类型元素的并查集操作

过去在做并查集的题目时,往往是将相同元素组成一个集合进行操作,最近,在练习中发现,原来并查集还可以操作类型不同的元素,具体来说,就是在统计敌对关系或者不同类型元素之间的关系时,比如食物链,也会用到并查集,但是分成多个集合是不现实的,因为元素之间的关系有可能交叉,因此,应该使用一个集合去操作


常用方法:每个元素除了有记录父节点的数组外,还要有一个记录与当前父节点的距离的数组,为什么说是当前呢?因为在并查集中最著名的就是通过指向根节点来压缩路径,因此在实际操作中,父节点会经常发生变化,而这个记录与当前父节点的距离的数组也要跟着变化,记录的所谓距离其实使用用来判断与父节点的关系的(具体看下面的例子),在操作过程中,这个记录距离的数组起着至关重要的作用


例1,关于敌对关系的并查集 poj1703

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.


题目大意是:A a b 是问你a和b是不是同一个帮派,D a b 告诉你a和b是不同的帮派


代码:


#include<stdio.h>
#include<string.h>
#define N 100005
int fa[N],dis[N];//fa记录父节点,dis记录到父节点的距离,用来判断与父节点的关系,0表示同帮派,1表示不同帮派
void init()
{
    for(int i=0;i<N;i++)
    {
        fa[i]=i;
        dis[i]=0;
    }
}
int find_set(int x)
{
    int fx=fa[x];
    if(fx!=x)
    {
        fa[x]=find_set(fx);//回溯使得父节点变为根节点
        dis[x]=(dis[x]+dis[fx])%2;//回溯时通过原父节点到根节点的距离更新子节点到根节点(也就是现在的父节点)的距离
    }
    return fa[x];
}
void Union(int x,int y)
{
    int fx=find_set(x);
    int fy=find_set(y);
    if(fx==fy)
        return ;
    fa[fy]=fx;//合并时只合并根节点
    dis[fy]=(dis[x]+1-dis[y])%2;//更新也只更新根节点,当子节点调用find_set时,会自动更新
}
int main()
{
    int t,n,m,x,y;
    char c[5];
    scanf("%d",&t);
    while(t--)
    {
        init();
        scanf("%d%d",&n,&m);
        for(int i=0;i<m;i++)
        {
            scanf("%s%d%d",c,&x,&y);
            if(c[0]=='A')
            {
                int fx=find_set(x);
                int fy=find_set(y);
                if(fx!=fy)//不同集合不可比
                    printf("Not sure yet.\n");
                else
                {
                    if(dis[x]==dis[y])//到根结点距离相等的子节点为同一帮派
                        printf("In the same gang.\n");
                    else
                        printf("In different gangs.\n");
                }
            }
            else
                Union(x,y);
        }
    }
    return 0;
}


例2,食物链关系,也就是多类元素的关系poj1182


Description

动物王国中有三类动物A,B,C,这三类动物的食物链构成了有趣的环形。A吃B, B吃C,C吃A。 
现有N个动物,以1-N编号。每个动物都是A,B,C中的一种,但是我们并不知道它到底是哪一种。 
有人用两种说法对这N个动物所构成的食物链关系进行描述: 
第一种说法是"1 X Y",表示X和Y是同类。 
第二种说法是"2 X Y",表示X吃Y。 
此人对N个动物,用上述两种说法,一句接一句地说出K句话,这K句话有的是真的,有的是假的。当一句话满足下列三条之一时,这句话就是假话,否则就是真话。 
1) 当前的话与前面的某些真的话冲突,就是假话; 
2) 当前的话中X或Y比N大,就是假话; 
3) 当前的话表示X吃X,就是假话。 
你的任务是根据给定的N(1 <= N <= 50,000)和K句话(0 <= K <= 100,000),输出假话的总数。 

Input

第一行是两个整数N和K,以一个空格分隔。 
以下K行每行是三个正整数 D,X,Y,两数之间用一个空格隔开,其中D表示说法的种类。 
若D=1,则表示X和Y是同类。 
若D=2,则表示X吃Y。

Output

只有一个整数,表示假话的数目。

Sample Input

100 7
1 101 1 
2 1 2
2 2 3 
2 3 3 
1 1 3 
2 3 1 
1 5 5

Sample Output

3


代码:

#include<stdio.h>
#include<string.h>
struct Animal
{
    int dis;//到父节点的距离,用来判断该元素与父节点的关系,0代表同类,1代表被父节点吃,2代表吃父节点
    int fa;//记录父节点
}a[51000];
void init()
{
    for(int i=0;i<51000;i++)
    {
        a[i].dis=0;
        a[i].fa=i;
    }
}
int find_set(int x)
{
    int fa=a[x].fa;
    if(fa!=x)
    {
        a[x].fa=find_set(fa);//回溯使得父节点为集合根节点,使得到父节点的距离变为到根节点的距离
        a[x].dis=(a[x].dis+a[fa].dis)%3;//通过回溯以及父节点更新该节点到根节点的距离
    }
    return a[x].fa;
}
void Union(int x,int y,int d)
{
    int fx=find_set(x);
    int fy=find_set(y);
    a[fy].fa=fx;//要更新的是根节点,只有更新根节点才能通过find_set更新根节点下所有子节点
    if(d==1)
        a[fy].dis=(a[x].dis+3-a[y].dis)%3;//同样,只要更新根节点
    else
        a[fy].dis=(a[x].dis+4-a[y].dis)%3;
}
int main()
{
    int d,x,y,n,k,c;
    scanf("%d%d",&n,&k);
    {
        init();
        c=0;
        for(int i=0;i<k;i++)
        {
            scanf("%d%d%d",&d,&x,&y);
            if(x>n||y>n)
            {
                c++;
                continue;
            }
            int fx=find_set(x);
            int fy=find_set(y);
            if(fx==fy)//只有在同一集合的前提下才能够判断,不同集合不存在真假
            {
                if(d==1)
                {
                    if(a[x].dis!=a[y].dis)
                        c++;
                }
                if(d==2)
                {
                    if(x==y)
                        c++;
                    else if(a[x].dis!=(a[y].dis+2)%3)
                        c++;
                }
            }
            else//不同集合就合并成一个集合,合并之后才会出现矛盾
                Union(x,y,d);
        }
        printf("%d\n",c);
    }
    return 0;
}

上述两个例子很好的看出,尽管在一个集合中存在不同类型的元素,当通过到根结点的距离(一般有多少类型元素,距离的最大值为元素类型数减1)可以很好的把元素分类,而且距离这个概念很好理解,如果想不通更新过程,可以在纸上画两个子结点,两个根结点,并相应的连上线,再通过简单的加减法,就能推出更新的公式了




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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值