带权并查集 POJ1988 POJ2492

单纯的并查集很简单,带权并查集还能解决更多的问题,才更好玩,来个题热身。对于下面的知识,现在就当你已经熟练掌握了递归和并查集的路径压缩。

POJ1988:题目链接 http://poj.org/problem?id=1988

题目大意:有N(N<=30,000)堆方块,开始每堆都是一个方块。方块编号1 – N. 有两种操作:

 M x y : 表示把方块x所在的堆,拿起来叠放到y所在的堆上。

 C x : 问方块x下面有多少个方块。

操作最多有 P (P<=100,000)次。对每次C操作,输出结果。

并查集里par数组是必不可少的,然后再通过维护一些别的信息即为路径加上权值来解决更复杂的问题。

对于此题,除了par数组,还要开设

sum数组:记录每堆一共有多少方块。 若parent[a] = a, 则sum[a]表示a所在的堆的方块数目。

under数组, under[i]表示第i个方块下面有多少个方块。 under数组在 堆合并和 路径压缩的时候都要更新。

先看初始化:

void Init(){
    for(int i=0;i<maxn;i++){
        par[i]=i;
        under[i]=0;
        sum[i]=1;
    }
}

重点就在under数组的更新,在路径压缩时更新应弄明白下面这点:

当前木块x下面应该有多少?应该是under[x](这是目前x所在堆中,x下面的木块个数),再加上x当前所在堆的根节点(即par[x])的under值,当然我们都知道根节点下面是没有木块的,under=0,但是在我们把x所在堆放到另一堆上之后,那么x所在堆的根节点(即par[x])的下面不就有木块了吗,那under值就不是0了。 因此,只要路径压缩时,对于当前木块x,其所在堆根节点的under值under[par[x]]是正确的,那么更新出的under[x]就是正确的。

在合并时更新: 这时更新很简单明显,把u所在堆全部放在v所在堆上,那么u下面的增加的木块个数就是v所在堆里木块的总数sum

再看路径压缩的查找:

/*注意这里,under[x]本来是从x往下到par[x]一共有多少个,那如果再加上
par[x]下面的个数,under[x]就更新正确了。我不管par[x]下面有多少,反正
路径压缩会使在执行这句话的时候让under[par[x]]保证是正确值。。
至于上面的y,它是层层递归得到的,很明显是路径压缩之后最终的根,所以
y只是用来par[x]=y,即把沿路遇到的点全部压缩到最终根(即真正的路径压缩)
这和更新under值没有半毛钱关系*/
int Find(int x){
    if(x==par[x]) return x;
    int y=Find(par[x]);
    under[x]+=under[par[x]];
    return par[x]=y;
}

可以看出under的更新是:先一直递归到该堆的最终根,然后从最终根向x的方向一步一步更新的,"由最终根能把under[par[x]]更新正确",那么under[x]自然就正确,那么x的子孙(即par[a]=x的a)也就能更新正确。

合并:

//在路径压缩完成后,就可以把u和v分别看出一个整体,
//如果把u放在v上面,自然v里的全部砖块都要算到under[x]里
void Union(int u,int v){
    if((u=Find(u))==(v=Find(v)))
        return ;
    under[u]+=sum[v];
    par[u]=v;sum[v]+=sum[u];
}

AC代码:

#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;

const int maxn=30000+10;
int sum[maxn],par[maxn],under[maxn];

void Init(){
    for(int i=0;i<maxn;i++){
        par[i]=i;
        under[i]=0;
        sum[i]=1;
    }
}

//注意这里,under[x]本来是从x往下到par[x]一共有多少个,那如果再加上
//par[x]下面的个数,under[x]就更新正确了。我不管par[x]下面有多少,
//反正路径压缩会使在执行这句话的时候让under[par[x]]保证是正确值。。
//至于上面的y,它是层层递归得到的,很明显是路径压缩之后最终的根,所以
//y只是用来par[x]=y,即把沿路遇到的点全部压缩到最终根(即真正的路径压缩)
//这和更新under没有半毛钱关系
int Find(int x){
    if(x==par[x]) return x;
    int y=Find(par[x]);
    under[x]+=under[par[x]];
    return par[x]=y;
}

//在路径压缩完成后,就可以把u和v分别看出一个整体,
//如果把u放在v上面,自然v里的全部砖块都要算到under[x]里
void Union(int u,int v){
    if((u=Find(u))==(v=Find(v)))
        return ;
    under[u]+=sum[v];
    par[u]=v;sum[v]+=sum[u];
}

int main()
{
    int i,j,p;
    char s[5];
    Init();
    scanf("%d",&p);
    while(p--){
        scanf("%s",s);
        if(s[0]=='M'){
            int a,b;
            scanf("%d%d",&a,&b);
            Union(a,b);
        }else {
            int x;
            scanf("%d",&x);
            Find(x);        //要先压缩一下,然后再查询
            printf("%d\n",under[x]);
        }
    }
    return 0;
}

Find函数完成路径压缩和信息更新的"四步曲":

①par[x]==x递归出口 ②一直递归找最终根并记录到y ③更新当前节点和父亲节点信息 ④返回par[x]=y,完成路径压缩。

在带权并查集中我们维护的信息一般都是当前节点x和其直接父亲par[x]的关系,所以在第三步时用par[x]的信息来更新x的信息。

下面再来看一个更直观的例子。

POJ2492 题目链接:http://poj.org/problem?id=2492

题目大意:共n个虫子,编号1-n,给出m个关系,用x,y描述,含义是:x和y有好感。问这m个关系给出后,是否能判断出有某一对虫子是同性恋,yes or no。

上面说过,在带权并查集里我们一般是维护当前节点x与其父亲的关系,在这看本题,应该会想到我们要维护什么样的信息,那就是: 用一个rel[]数组,来维护x和par[x]的性别关系,rel[x]=0表示x和par[x]是同性,rex[x]=1表示是异性,所以我们在维护并查集时还要维护这个rel信息。

先看初始化:

void Init(int n){
        for(int i=0;i<=n;i++){
            par[i]=i,rel[i]=0;
        }
    }

一开始每个虫子都和自己是同性,能理解。

在Find函数路径压缩时,rel[]又该怎么更新呢?

我们先看我们的目的:因为我们这是路劲压缩,即把所有经过的节点的par都指向最终根(设为y),所以在Find(x)时,最终rel[x]表示的是x和y的性别关系(当然最终par[x]会等于y,符合rel[]的定义)。由上面蓝色的话,我们可以知道,Find函数中,par[x]的rel[]是确定的(不要问为什么,上面已经说的很清楚了,看蓝色字的""号部分),因此,我们现在我们就是已知x和par[x]的关系,par[x]和y的关系,求x和y的关系,很简单吧。列个表给看一下。

这个表自己好好体会,然后一想一划就能明白。

我们可以看出新的rel[x]可以由之前的rel[x]和rel[par[x]],且等于rel[x]^rel[par[x]]。  我们的目的就达到了。

再看代码:

int Find(int x){
        if(x==par[x]) return x;
        int y=Find(par[x]);
        rel[x]=rel[x]^rel[par[x]];
        return par[x]=y;
    }

四步曲还是一样。这样就维护了rel[x]。

对于给出的一对虫子,怎么判断他们是不是同性的呢?

这就看Union合并函数了,先帖代码,接着分析。

bool Union(int u,int v){    //返回true:同性;否则异性
        int pu=Find(u),pv=Find(v);
        if(pu==pv) return rel[u]==rel[v];   //如果在同一个集合里,那么两者与祖先的性别关系一样的话说明两者同性
        rel[pv]=!(rel[u]^rel[v]);
        par[pv]=pu;
        return false;
    }

注意Union函数的返回值含义。

设pu=Find(u),pv=Find(v),对于给定的一对虫子u和v,有两种情况:

1.两者在同一集合里,pu==pv:那就好办了,也不能合并,而且能一下就能判断出两者的性别关系,他们的rel[]都是相对于同一根节点的,如果你和你爸是同性,我和你爸是同性,那么咱俩的性别关系不就是同性吗。

2.两者不在同一集合里,pu!=pv: 那么u,v的关系我们就认为是异性,并以此为据来合并两个集合,合并两个集合就是其实就是合并pu和pv啊,我们就把以pu为根的集合合并到以pv为根的集合,即par[pu]=pv,但是我们还要更新rel[pu],即pu和pv的关系,他们的关系怎么确定呢?   首先我们知道u和pu的关系为rel[u],v和pv的关系为rel[v],且我们还要保证u和v异性,由这三个关系就能确定pu和pv的关系!!列个表看一下:

那第一个简单说明一个,u和pu是同性,v和pv也是同性,u和v是异性,则pu和pv自然是异性。

因此rel[pu]=!(rel[u]^rel[v])。

大概就是这么多。

AC代码:

/*
带权并查集
*/
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;

const int maxn=2000+10;

typedef struct WUFSet{
    int par[maxn],rel[maxn];    //0同性,1异性
    void init(int n){
        for(int i=0;i<=n;i++){
            par[i]=i;rel[i]=0;
        }
    }
    int Find(int x){
        if(x==par[x]) return x;
        int y=Find(par[x]);
        rel[x]=rel[x]^rel[par[x]];
        return par[x]=y;
    }
    bool Union(int u,int v){    //返回true:同性;否则异性
        int pu=Find(u),pv=Find(v);
        if(pu==pv) return rel[u]==rel[v];   //如果在同一个集合里,那么两者与祖先的性别关系一样的话说明两者同性
        rel[pv]=!(rel[u]^rel[v]);
        par[pv]=pu;
        return false;
    }
}WUFSet;

int main()
{
    int i,T,n,m,kcase=1;
    WUFSet s;
    scanf("%d",&T);
    while(T--){
        scanf("%d%d",&n,&m);
        s.init(n);
        bool yes=true;
        while(m--){
            int a,b;
            scanf("%d%d",&a,&b);
            if(s.Union(a,b))
                yes=false;
        }
        printf("Scenario #%d:\n",kcase++);
        if(yes)
            printf("No suspicious bugs found!\n\n");
        else
            printf("Suspicious bugs found!\n\n");
    }
    return 0;
}

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-12 15:20:32

带权并查集 POJ1988 POJ2492的相关文章

poj2492 A Bug&#39;s Life【带权并查集】

题目链接:http://poj.org/problem?id=2492 题目描述:找基佬游戏(汗-_-b)有个hentai科学家研究虫子种群,不断地给出二元组xy,表示x和y是异性交往,但是可能会出现矛盾(找到基佬),比如1与2是异性恋,2与3是异性恋,却又告诉你1和3是异性恋.问种群中存不存在基佬败类 思路:与poj1182“食物链”几乎一样,还简单一点,毕竟只有两类物品.par[i]表示父节点,d[i]表示偏移量,0为同性,1为异性.不过要注意的一点是所有合并的过程要对二取模,比如x找到根结

POJ-2492.A Bug&#39;s Life(带权并查集)

A Bug's Life Time Limit: 10000MS   Memory Limit: 65536K Total Submissions: 48043   Accepted: 15483 Description Background Professor Hopper is researching the sexual behavior of a rare species of bugs. He assumes that they feature two different gender

并查集练习2(带权并查集)

明天旅游去爬山逛庙玩,今天练一天然后早早睡觉啦~ poj1703 Find them, Catch them (带权并查集) 1 #include<cstdio> 2 const int N=1e5+1; 3 int f[N]; 4 int r[N];//表示与父节点的关系,0同类,1不同类 5 int n; 6 void init(){ 7 for(int i=1;i<=n;++i){ 8 f[i]=i; r[i]=0; 9 } 10 } 11 int fin(int x){ 12 i

hdu3038(带权并查集)

题目链接: http://acm.split.hdu.edu.cn/showproblem.php?pid=3038 题意: n表示有一个长度为n的数组, 接下来有m行形如x, y, d的输入, 表示从第x,个元素到第y个元素的和为d(包括x, 和y), 问m行输入里面有几个是错误的(第一个输入是正确的); 思路: 很显然带权并查集咯,我们可以用距离的概念代替和的概念比较好理解一点,d表示x到y的和即x到y的距离; 可以用rank[x]表示x到其父亲节点的距离,  将正确的距离关系合并到并查集中

【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句话有的是真的,有的是假的.当一句话满足下列三条之

【poj 1988】Cube Stacking(图论--带权并查集 模版题)

题意:有N个方块,M个操作{“C x”:查询方块x上的方块数:“M x y”:移动方块x所在的整个方块堆到方块y所在的整个方块堆之上}.输出相应的答案. 解法:带权并查集.每堆方块作为一个集合,维护3个数组:fa[x]表示x方块所在堆的最顶部的方块:d[x]表示x方块所在堆的最底部的方块:f[x]表示x方块方块x上的方块数. 1 #include<cstdio> 2 #include<cstdlib> 3 #include<cstring> 4 #include<

Lightoj1009 Back to Underworld(带权并查集)

转载请注明出处: http://www.cnblogs.com/fraud/          ——by fraud Back to Underworld Time Limit:4000MS     Memory Limit:32768KB     64bit IO Format:%lld & %llu Description The Vampires and Lykans are fighting each other to death. The war has become so fierc

[NOIP摸你赛]Hzwer的陨石(带权并查集)

题目描述: 经过不懈的努力,Hzwer召唤了很多陨石.已知Hzwer的地图上共有n个区域,且一开始的时候第i个陨石掉在了第i个区域.有电力喷射背包的ndsf很自豪,他认为搬陨石很容易,所以他将一些区域的陨石全搬到了另外一些区域. 在ndsf愉快的搬运过程中,Hzwer想知道一些陨石的信息.对于Hzwer询问的每个陨石i,你必须告诉他,在当前这个时候,i号陨石在所在区域x.x区域共有的陨石数y.以及i号陨石被搬运的次数z. 输入描述: 输入的第一行是一个正整数T.表示有多少组输入数据. 接下来共有

hdu 1558 Segment set【基础带权并查集+计算几何】

Segment set Time Limit: 3000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submission(s): 3599    Accepted Submission(s): 1346 Problem Description A segment and all segments which are connected with it compose a segment set