并查集> <

【原理】

每个集合用一棵树来表示。树根的编号就是整个集合的编号,每个节点存储它的父节点,即p[x]是x的父节点。

元素本身的值是固定不变的,但是元素所属的集合是可以变化的。

两个重要信息:

(1)元素的值

(2)集合的标号(祖宗)

【操作】

1.将两个集合合并

2.询问两个元素是否在同一个集合中

【常见问题】

1.判断树根( if(p[x]==x) )

2.求x的集合编号

3.合并集合(p[x]=y),让a的祖宗认b的祖宗当爸爸 ^-^

例1 合并集合

【分析】

求x所在集合的编号的朴素做法

while(p[x]!=x) x=p[x];

但是这样的时间复杂度会很高,因此需要优化。优化方法为路径压缩,即每次遍历一棵树都让查找的元素上面的元素指向根节点(祖宗变父亲),这样的时间复杂度差不多是O(1)

【代码】

#include <bits/stdc++.h>
using namespace std;
const int N=100010;
int p[N];//表示x的父亲节点
int n,m;
int find(int x){
    if(p[x]!=x) p[x]=find(p[x]);
    return p[x];//返回根节点+路径压缩
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=0;i<n;i++) p[i]=i;//每个人刚开始都是自己的papa,自成一家
    while(m--){
        char op[2];
        int a,b;
        scanf("%s%d%d",op,&a,&b);
        if(op[0]=='M') p[find(a)]=find(b);
        else{
            if(find(a)!=find(b)) puts("No");
            else puts("Yes");
        }
    }
    return 0;
}

例2 连通块中点的数量

【分析】

可以发现,本题除了第三种类型的询问都和例1相同。

求某点所在连通块的点的数量(家族人数),只需要另开一个数组size用来存储祖宗的后人数量,所以size只有祖节点的有意义。
要特别注意所有处理size的地方,都要“归根结底”。

【代码】

#include <bits/stdc++.h>
using namespace std;
const int N=100010;
int p[N],s[N];//papa && size
int n,m;
int find(int x){
    if(p[x]!=x) p[x]=find(p[x]);
    return p[x];
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        p[i]=i;
        s[i]=1;//刚开始自成一家,只有一个人,非常lonely
    }
    while(m--){
        char op[5];
        int a,b;
        scanf("%s",op);
        if(op[0]=='C'){
            scanf("%d%d",&a,&b);
            if(find(a)==find(b)) continue;//特判,如果a和b已经在同一个集合里,直接跳过
            s[find(b)]+=s[find(a)];//只需要维护b
            p[find(a)]=find(b);//有爸爸了,,
        }
        else if(op[1]=='1'){
            scanf("%d%d",&a,&b);
            if(find(a)==find(b)) puts("Yes");
            else puts("No");
        }
        else{
            scanf("%d",&a);
            printf("%d\n",s[find(a)]);//a的根节点的size就是整个连通块的size
        }
    }
    return 0;
}

例3 食物链

【分析】

方法一:

1.首先要捋清楚动物之间的关系,本题中只有三个关系,a吃b,b吃c,c吃a

2.我们用p[x]表示x的根节点,而d[x](初始化为0,因为刚开始表示自己到自己)表示的则是x到父节点的距离,不是根节点!!但由于在find(x)中进行了路径压缩,当查询某个节点i时,如果节点i的父节点不为根节点,就会进行递归调用,让i路径上的每一个节点都指向根节点,这样一来d就成了到根节点的距离,但它其实是到父节点T_T

3.关于距离

4.判断x和y同类:

(1)首先确定x和y的根节点是否相同,然后再判断距离%3是否等于0,不等于0说明不是同类

(2)如果根节点不相同,说明x和y还没有确定关系,将两个动物合并一下

5.判断x吃y

(1)x与y的根节点相同时x到y的距离减1不等于0(按道理来说应该是等于0的),说明矛盾

(2)同上

【代码】

#include <bits/stdc++.h>
using namespace std;
const int N=50010;
int p[N],d[N];
int n,k;
int find(int x){
    if(p[x]!=x){
        int u=find(p[x]);
        d[x]+=d[p[x]];
        p[x]=u;
    }
    return p[x];
}
int main(){
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++) p[i]=i;
    int res=0;
    while(k--){
        int t,x,y;
        scanf("%d%d%d",&t,&x,&y);
        if(x>n||y>n) res++;//超过了范围 假的
        else{
            int px=find(x),py=find(y);
            if(t==1){
                if(px==py && (d[x]-d[y])%3) res++;//能吃 假的
                else if(px!=py){
                    p[px]=py;
                    d[px]=d[y]-d[x];
                }
            }
            else{
                if(px==py && (d[x]-d[y]-1)%3) res++;//同类 假的
                else if(px!=py){
                    p[px]=py;
                    d[px]=d[y]-d[x]+1; 
                }
            }
        }
    }
    printf("%d\n",res);
    return 0;
}

方法二:

1.在弄清楚a,b,c三者之间的关系之后,我们可以知道对于任意一种动物,都存在着相应的捕食域和天敌域。

存在关系的判断

(1) x与y是同类

如果x存在于y的捕食域中或y存在于x的捕食域中(find(x+2*n)==find(y) || find(x)==find(y+2*n)),为假话。

(2)x吃y

如果x存在于y的捕食域中或x与y相等或x与y有共同的祖宗节点(x==y||find(x)==find(y)||find(x)==find(y+n)),为假话。

2.如果语句为真,对于同类者,则将其及其捕食域天敌域都合并,一体了就是说;对于吃与被吃者(x吃y),将x与y的天敌域、x的捕食域与y、x的天敌域与y的捕食域合并

3.注意p数组的大小,这样看应该有三个域,所以有int p[N*3] i<=n*3

【代码】

#include <bits/stdc++.h>
using namespace std;
const int N=50010;
int p[N*3];
int n,k;
int find(int x){
    if(p[x]!=x) p[x]=find(p[x]);
    return p[x];
}
void merge(int x,int y){
    p[find(x)]=find(y);
}//合并
int main(){
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n*3;i++) p[i]=i;
    int res=0;
    while(k--){
        int d,x,y;
        cin>>d>>x>>y;
        if(x>n || y>n) res++;//边界
        else{
            if(d==1){
                if(find(x+2*n)==find(y) || find(x)==find(y+2*n)) res++;
                else{
                    merge(x,y);
                    merge(x+n,y+n);
                    merge(x+2*n,y+2*n);
                }
            }
            else{
                if(x==y||find(x)==find(y)||find(x)==find(y+n)) res++;
                else{
                    merge(x+n,y);
                    merge(x+2*n,y+n);
                    merge(x,y+2*n);
                }
            }
        }
    }
    printf("%d\n",res);
    return 0;
}

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值