并查集

本文详细介绍了并查集算法的基本概念、实现原理及其在解决实际问题中的应用。通过多个实例展示了如何利用并查集处理集合合并及查找问题,并介绍了路径压缩、加权并查集等高级技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

所谓并查集,就是对许多集合做多次合并与查找。

其关键就是递归寻找father与进行路径压缩,基于并查集的很多题目都会在压缩上做文章。

洛谷p3367并查集模版题

#include<bits/stdc++.h>
using namespace std;
int n,m,p,fa[10010];
inline int getfather(int x)//寻找father并进行路径压缩
{
    if(fa[x]==x)return x;//递归边界
    fa[x]=getfather(fa[x]);//压缩
    return fa[x];
}

inline void judge(int x,int y)//判断是否在一个集合
{
    int fx,fy;
    fx=getfather(x);
    fy=getfather(y);
    if(fx==fy)printf("Y\n");
    else printf("N\n");
    return;
}

inline void merge(int x,int y)//合并x和y所在的两个集合
{
    int fx,fy;
    fx=getfather(x);
    fy=getfather(y);
    fa[fx]=fy;
    return;
}

inline void work()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)fa[i]=i;
    for(int i=1;i<=m;i++)
    {
        int t,x,y;
        scanf("%d%d%d",&t,&x,&y);
        if(t==1)merge(x,y);
        else judge(x,y);
    }
    return;
}

int main()
{
    work();
    return 0;
}
洛谷p1551亲戚同理

接下来,就需要一些绝妙的操作了

洛谷p1196银河英雄

需要查找战舰间的距离是最难的一点,当压缩路径时,点x需加上它father的num。

#include<bits/stdc++.h>
using namespace std;
struct ship
{
    int fa,num,s;//s代表该战舰开头的队列长度,num为第几艘
}a[30010];
int n,m,p;
inline int getfather(int x)
{
    if(a[x].fa==x)return x;
    int pre=a[x].fa;//暂时记忆为了接下来的处理
    int f=getfather(a[x].fa);
    a[x].fa=f;
    a[x].num+=a[pre].num;//更新x的位置
    return a[x].fa=a[a[x].fa].fa;
}

inline void judge(int x,int y)
{
    int fx,fy;
    fx=getfather(x);
    fy=getfather(y);
    int ansx,ansy,ans;
    ansx=a[x].num;
    ansy=a[y].num;
    ans=abs(ansx-ansy)-1;//计算距离
    if(fx==fy)
    printf("%d\n",ans);
    else printf("-1\n");
    return;
}

inline void merge(int x,int y)
{
    int fx,fy;
    fx=getfather(x);
    fy=getfather(y);
    a[fx].num=a[fy].s;//将y所在队列接到x所在队列之后
    a[fy].s+=a[fx].s;
    a[fx].fa=fy;
    return;
}

inline void work()
{
    scanf("%d",&p);
    char temp[10];
    gets(temp);
    for(int i=1;i<=30000;i++)
    {
        a[i].fa=i;
        a[i].s=1;
        a[i].num=0;
    }
    for(int i=1;i<=p;i++)
    {
        int x,y;char c;
        scanf("%c%d%d",&c,&x,&y);
        if(c=='M')merge(x,y);
        else judge(x,y);
        gets(temp);//防止回车被读入
    }
}

int main()
{
    work();
    return 0;
}
洛谷p1525关押罪犯

对于两者不能在一个集合里的关系怎么办呢,这个时候可以采用补集。

若x与y敌对,则另x与(y+n)为同一集合。

#include<bits/stdc++.h>
using namespace std;
struct criminal
{
    int x,y,w;
}c[100010];
int fa[40010],n,m,num=0;
inline int getfather(int x)
{
    if(fa[x]==x)return x;
    fa[x]=getfather(fa[x]);
    return fa[x];
}

inline bool mycmp(criminal a,criminal b)
{
    return(a.w>b.w);
}

inline void init()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
    scanf("%d%d%d",&c[i].x,&c[i].y,&c[i].w);
    sort(c+1,c+m+1,mycmp);//为了矛盾尽可能小
    return;
}

inline void work()
{
    for(int i=1;i<=2*n;i++)fa[i]=i;//两倍!
    for(int i=1;i<=m;i++)
    {
        int fx=getfather(c[i].x);
        int fy=getfather(c[i].y);
        if(fx==fy)//x与y同一集合说明他们都与某人敌对,无法再分开
        {
            printf("%d",c[i].w);
            exit(0);
        }
        fa[fx]=getfather(c[i].y+n);//敌对,getfather是为了让补集进行压缩
        fa[fy]=getfather(c[i].x+n);//double敌对
    }
    printf("0");
    return;
}

int main()
{
    init();
    work();
    return 0;
}

洛谷p2024食物链

本来想用两个补集,后来发现,emmm……

然后就采用了加权并查集。

对于结点x,fa[x][0]存储它的father,fa[x][1]存储它和father间的关系。

0表示为同一类,1表示

#include<bits/stdc++.h>
using namespace std;
int fa[60010][2],n,ans,k;
inline void init()
{
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++)fa[i][0]=i,fa[i][1]=0;
    return;
}

inline int getfather(int x)
{
    if(fa[x][0]==x)return x;
    int f=fa[x][0];
    fa[x][0]=getfather(fa[x][0]);
    fa[x][1]=(fa[x][1]+fa[f][1])%3;
    return fa[x][0];
}

inline void unio(int x,int y,int t)
{
    if(t==1)
    {
        int fx=getfather(x);
        int fy=getfather(y);
        if(fx==fy&&fa[x][1]!=fa[y][1])
        {
            ans++;
            return;
        }
        else
        {
            fa[fx][0]=fy;
            fa[fx][1]=(fa[y][1]-fa[x][1]+3)%3;
        }
    }
    else
    {
        int fx=getfather(x);
        int fy=getfather(y);
        if(fx==fy&&(fa[x][1]!=(fa[y][1]+1)%3))
        {
            ans++;
            return;
        }
        else
        {
            fa[fx][0]=fy;
            fa[fx][1]=(fa[y][1]-fa[x][1]+4)%3;
        }
    }
    return;
}

inline void work()
{
    for(int i=1;i<=k;i++)
    {
        int x,y,t;
        scanf("%d%d%d",&t,&x,&y);
        if(x>n||y>n||(t==2&&x==y))
        {
            ans++;
            continue;
        }
        unio(x,y,t);
    }
    printf("%d",ans);
    return;
}

int main()
{
    init();
    work();
    return 0;
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值