HDU 1829 A Bug‘s Life 带权并查集&&带偏移量的写法

                 翻一翻以前做过的题目,突然发现这道题目还没有写题解的,现在补上好了。

                 这道题目我用了两种写法,一种是偏移量,另外一种是带权,老实说我感觉偏移量的写法更好写一些?直接啪啪啪啪上来一下就打完了,还不用考虑改写find函数。

                Tips:偏移量的写法是开了一个n<<1大小的数组,前n个表示性别为公,后n个表示性别为母,每一次合并编号为x,y的两只虫子的时候,实际上就是在数组里面合并x和y+n以及y和x+n这两种情况,合并四个集合,但是一旦x,y的父亲相同,也就是代表他们处在同一个集合里面的时候肯定违反了题目条件,这个时候我们就可以下结论了。

#include<cstdio>
#include<cstring>

using namespace std;

int t,n,k,sets[6010],x,y,ca=1;
int _find(int x){return x==sets[x]?x:sets[x]=_find(sets[x]);}
void _merge(int x,int y){sets[_find(y)]=_find(x);}
bool flag;

int main(){
    scanf("%d",&t);
    while(t--){
        scanf("%d%d",&n,&k);
        for(int i=1;i<=n<<1;++i)
            sets[i]=i;
        while(k--){
            scanf("%d%d",&x,&y);
            if(_find(x)==_find(y)){
                flag=true;
                while(k--)scanf("%d%d",&x,&y);
                break;
            }
            else
                _merge(x,y+n),_merge(x+n,y);
        }
        printf("Scenario #%d:\n%s\n\n",ca++,flag?"Suspicious bugs found!":"No suspicious bugs found!");
        flag=false;
    }
    return 0;
}


              接下来介绍带权的写法,其实带权的写法就是除了基础的并查集数组以外额外再增加一个表示每个点的状态的数组,这里我把这个表示状态的数组叫做level,来表示每个节点与其根节点的性别是否相同,当然了,重点就是在于关系的推导,此时原来的find函数以及merge函数都需要做相应的修改,在find函数里运用路径压缩的时候还需要修改level的值,同时在合并的时候也需要修改level的值,也就是说关键在于确定level值的变化。
             而我们在修改一个节点的level值的时候所依赖的就是他自己原先的值以及他自己父亲的值,这里的情况还是比较简单的,只有两种情况,知道了父亲的和根节点的关系以及自己原来和根节点的关系,当然可以推导出现在自己和根节点的关系,在纸上稍微画一画就可以知道。
             然后需要处理一下的地方就就是在merge操作的时候,我在这里提醒一点,在merge操作的时候只需要更改根节点的level就可以了,举个例子,我把a,b两个集合merge一起,让b成为a的子树,那么此时我只需要修改b的level值就可以,你可能会提出疑问,那么b原来的子树岂不是在合并之后level值就不对了?没关系,我们在find函数里面进行路径压缩的时候自动会把他修正的,自己可以手动模拟一下。然后就是推导被合并的根节点的level值怎么变化的问题了,其实也还是需要靠自己手动推。。我自己在之前没有明白带权并查集需要怎么写,所以在此特地列出来,至于公式推不出来,还是看看我下面的代码吧。
            PS:在写这个的时候没有初始化,也就是在最开始的时候没有把每一个节点的根节点刷成自己,所以在find和merge时候做了特判。
#include<cstdio>
#include<cstring>

using namespace std;

int t,n,k,sets[2010],level[2010],x,y,ca=1;
bool flag;
int _find(int x);
void _merge(int x,int y);

int main(){
    scanf("%d",&t);
    while(t--){
        printf("Scenario #%d:\n",ca++);
        scanf("%d%d",&n,&k);
        while(k--){
            scanf("%d%d",&x,&y);
            if(sets[x]!=0&&sets[y]!=0&&_find(x)==_find(y)&&level[x]==level[y]){
                flag=true;
                while(k--)
                    scanf("%d%d",&x,&y);
                break;
            }
            else
                _merge(x,y);
        }
        printf("%suspicious bugs found!\n\n",(flag)?"S":"No s");
        flag=false;
        memset(level,0,sizeof(int)*(n+1));
        memset(sets,0,sizeof(int)*(n+1));
    }
    return 0;
}

int _find(int x){
    if(x==sets[x])
        return x;
    else{
        int te=sets[x];
        sets[x]=_find(sets[x]);
        level[x]=(level[te]+level[x])&1;
        return sets[x];
    }
}

void _merge(int x,int y){
    if(sets[x]==0){
        if(sets[y]==0){
            sets[x]=sets[y]=x;
            level[y]=1;
        }
        else{
            sets[x]=_find(y);
            level[x]=1-level[y];
        }
    }
    else{
        if(sets[y]==0){
            sets[y]=_find(x);
            level[y]=1-level[x];
        }
        else{
            int a=_find(x),b=_find(y);
            if(a!=b){
                sets[b]=a;
                level[b]=(level[x]+level[y]==1)?0:1;
            }
        }
    }
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值