并查集—应用

有人说:并查集好写,好用,就是没什么地方用。的确,并查集应用不是特别广,但作为一种优质算法,这里还是要多说几句。并查集的问题主要分成两大类:带权并查集,种类并查集。


一、带权并查集

带权并查集就是让每个节点除了记录自己父亲以外,还记录一些其它的东西(如:集合的大小),通过它记录的信息来解决题目。


例题1:(来源: caioj 1095)

1. M i j :合并指令,i和 j是指令涉及的战舰编号。该指令是将i号战舰所在的整个战舰接至第 j号战舰所在的战舰队列的尾部。
2. C i j :询问指令,i和 j是指令涉及的战舰编号。第 i 号战舰与第 j 号战舰当前是否在同一列中,如果在同一列中,那么它们之间布置有多少战舰。 


看到“合并”,很容易想到并查集,这题合并的战舰要求保持有序,那对并查集的要求有点高了。

思路:我们可以对战舰组进行编号,战舰头记为1,战舰尾在舰队中的编号特别记录在tail中,由战舰头负责管理。在战舰组中的每艘战舰要记住该战舰组的头,它就像是舰队的首领,同时,不能忘记自己在战舰组中的位置。这样,对于每次询问,我们只要先看看这两艘战舰是否在同一战舰组,是,则输出它们的位置差。


代码:

#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
 
int fa[30010],dis[30010],tail[30010];//dis记录战舰在舰队中的位置  tail记录该战舰的尾战舰在舰队中的编号,也能反映舰队的大小 
char c[5];
 
int find_fa(int x)
{
    if(fa[x]==x) return fa[x];
    int f=find_fa(fa[x]);//先让舰队头更新当前时局 
    dis[x]=dis[x]+dis[fa[x]]-1;//自己再更新 
    fa[x]=f;
    return fa[x];
}
 
int main()
{
    int T;
    scanf("%d",&T);
    for(int i=1;i<=30000;i++)
    {
        fa[i]=i;
        dis[i]=1;
        tail[i]=1;
    }
    while(T--)
    {
        int x,y;
        scanf("%s%d%d",c,&x,&y);
        if(c[0]=='M')
        {
            int fx=find_fa(x),fy=find_fa(y);
            fa[fx]=fy;
            dis[fx]=tail[fy]+1;//fx更新自己在舰队中的位置 
            tail[fy]=tail[fy]+tail[fx];//fy更新扩展后的舰队的信息
        }
        else
        {
            int fx=find_fa(x),fy=find_fa(y);
            if(fx!=fy)
            {
                printf("-1\n");
                continue;
            }
            printf("%d\n",abs(dis[x]-dis[y])-1);
        }
    }
    return 0;
}


二、种类并查集

种类并查集就是已经给出了两两关系判断方式的并查集,要求你根据这种判断方式来求出其中某两个的关系。通常我们需要用一个re数组,记录下我与我父亲的关系,此外还要求出关系的转移式,使得每个集合内的关系和合并集合时的关系能够准确无误的展现。


例题2:(来源: caioj 1096)

动物王国中有三类动物A,B,C,这三类动物的食物链构成了有趣的环形。A吃B, B吃C,C吃A。
第一种说法是“1 X Y”,表示X和Y是同类。 
第二种说法是“2 X Y”,表示X吃Y。
当一句话满足下列三条之一时,这句话就是假话,否则就是真话。当前的话与前面的某些真的话冲突,就是假话;当前的话中X或Y比N大,就是假话;当前的话表示X吃X,就是假话。 输出假话的总数。


如果用一个belong数组简单记录下每种动物的属性,在合并两条食物链时,会显得无从下手,既然又栽在了合并的问题上,为什么不考虑并查集呢?

思路:用re数组记录下我与父亲的关系:0:我与父亲同类,或者我没有父亲;1:我吃父亲;2:我被父亲吃。


如图(箭头从吃指向被吃),我们来理一理关系的计算法则。

D与A的关系应该为0,其实是(re[x]+re[fa[x]])%3的结果。由此,我们得到,一条食物链上的关系可以用加法解决。

A与C的关系应该为1(即A吃C),其实是(3-re[x])%3的结果。由此,我们得到,若将关系的发出者交换,关系为(3-re[x])%3

B与C的关系应该为2(即B被C吃),根据结论2,我们可以把B与A的关系和C与A的关系,转换为B与A的关系和A与C的关系,现在B和C就在一条顺着的关系上了。再根据结论1,我们得到B与C的关系为(3-re[y])%3+re[x],整理得(re[x]-re[y]+3)%3。由此,我们求得了与祖先有直接关系的动物跨过祖先后,它们关系为(re[x]-re[y]+3)%3


接下来,如果发现X与Y在一条食物链里,我们让X与Y直接与父亲相连(路径压缩),那么我们通过 (re[x]-re[y]+3)%3 可以找到X与Y的关系;特别的,当X或Y没有父亲时,也不影响通过该公式求出两个动物间的关系。


代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
 
int n,k,ans=0;
int fa[50010],re[50010];//x与fa[x]的关系(0,1,2) 
 
int find_fa(int x)
{
    if(fa[x]==x) return fa[x];
    int f=find_fa(fa[x]);
    re[x]=(re[x]+re[fa[x]])%3;//关系公式 
    fa[x]=f;
    return fa[x];
}
 
int main()
{
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++)
    {
        fa[i]=i;
        re[i]=0;
    }
    while(k--)
    {
        int d,x,y;
        scanf("%d%d%d",&d,&x,&y);
        if(x>n||y>n) ans++;
        else if(d==2&&x==y) ans++;
        else
        {
            int fx=find_fa(x),fy=find_fa(y);
            if(fx==fy)//在一条记录好了的食物链中 
            {
                if((re[x]-re[y]+3)%3!=d-1) ans++;//关系公式 
            }
            else//以前没有记录过关系,此话为真,所以记录下它们的关系 
            {
                fa[fx]=fy;
                if(d==1) re[fx]=(re[y]-re[x]+3)%3;//关系公式 
                else re[fx]=(1-re[x]+re[y]+3)%3;//关系公式 
            }
        }
    }
    printf("%d",ans);
    return 0;
}


总结:对于种类并查集,先制定好种类编号,用加法的形式检验。合格后,继续寻找其它的关系公式。最后,应用到并查集模版中。



通过以上两题,有没有发现,对于复杂些的并查集,它的find_fa函数总是有以下格式:

int find_fa(int x)
{
    if(fa[x]==x) return fa[x];
    int f=find_fa(fa[x]);
    
    //随意干些奇奇怪怪的事 
    
    fa[x]=f;
    return fa[x];
}

要记得先让旧父亲寻找新父亲,接着处理自己的事,最后才更新自己的最新父亲。因为要使旧父亲的情况与最新情况同步,自己才能把与新父亲的关系处理好,进而抛弃旧父亲,认最新的父亲。


推荐:《并查集—入门》http://blog.csdn.net/a_bright_ch/article/details/77161640


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值