并查集扩展

本文详细介绍了带权并查集的概念,包括其在路径压缩基础上添加权重的操作,并提供了实例解析。同时,文章探讨了扩展并查集的应用,如在处理敌友关系、性别关系等复杂关系中的作用。通过具体的题目实例,如P1196《银河英雄传说》和P1892《团伙》,展示了带权并查集和扩展并查集在解决实际问题中的策略和技巧。此外,还列举了其他相关题目,如P4047《部落划分》和P5937《ParityGame》,进一步阐述了并查集在最小生成树、奇偶性判断等问题中的应用。

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

添加链接描述## 带权并查集*
顾名思义带权并查集是在并查集的基础上加上权边,通过递归修改到根节点的距离

  1. get函数中的d[]数组表示到根节点的距离
  2. Merge函数中需根据题目改变
  3. get函数中,通过递归d[X]+=d[fa[x]] 从而更新到根节点距离
int get(int x)
{
    if(x==fa[x]) return x;
    int root=get(fa[x]);
    d[x]+=d[fa[x]];
    return fa[x]=root;
}
void Merge(int x,int y,int z)
{
    int fx=get(x),fy=get(y);
        fa[fx]=fy;
        //d[fx]=z+d[y]-d[x]; 不同题目此地方不同
}

例题: P1196 [NOI2002] 银河英雄传说

#include<bits/stdc++.h>
using namespace std;
typedef long long ll ;
#define fr(i,n) for(int i=1;i<=n;i++)
const int mod = 107,N=5e5+5;
ll n,m,len=0,tot=0,fa[N],Size[N],d[N];
inline ll read()
{
    ll x=0,f=1; char ch=getchar();
    while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}
    while(ch>='0'&& ch<='9') {x=x*10+ch-'0';ch=getchar();}
    return f*x;
}
int get(int x)
{
    if(x==fa[x]) return x;
    int root=get(fa[x]);
    d[x]+=d[fa[x]];
    return fa[x]=root;
}
void Merge(int x,int y)
{
    int fx=get(x),fy=get(y);
    fa[fx]=fy;
    d[fx]=Size[fy];  ///更新到根节点的距离
    Size[fy]+=Size[fx];
}
int main()
{
    n=read();
    fr(i,n) fa[i]=i,d[i]=0,Size[i]=1;
    fr(i,n)
    {
        char ch;int x,y;
        cin>>ch>>x>>y;
        if(ch=='M')
            Merge(x,y);
        if(ch=='C')
            if(get(x)==get(y))
               cout<<abs(d[y]-d[x])-1<<endl; ///y到x的距离
            else cout<<-1<<endl;
    }
    return 0;
}

扩展并查集

扩展并查集在原先并查集的基础上加上了从n+1~2*n,扩展x的另外一个并查集,例如敌人的敌人是朋友这样的问题,可以用扩展并查集.
扩展并查集的用途很多,若一个并查集只能用于传递一种关系,而传递多种关系时候需要增加并查集。此时扩展并查集用于传递多种关系例如:敌友关系,食物关系,性别关系.
例题P1892 [BOI2003]团伙
此题时扩展并查集的经典题目:敌人的敌人是朋友,扩展并查集

#include<bits/stdc++.h>
using namespace std;
typedef long long ll ;
#define fr(i,n) for(int i=1;i<=n;i++)
const int mod = 107,N=5e4+5;
ll n,m,len=0,tot=0,fa[N*2+5];
map<int,int>mp;
inline ll read()
{
    ll x=0,f=1; char ch=getchar();
    while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}
    while(ch>='0'&& ch<='9') {x=x*10+ch-'0';ch=getchar();}
    return f*x;
}
int get(int x)
{
    if(x==fa[x]) return x;
    return fa[x]=get(fa[x]);
}
void Merge(int x,int y)
{
    x=get(x),y=get(y);
    fa[x]=y;
}
int main()
{
    n=read(),m=read();
    fr(i,2*n) fa[i]=i;
    fr(i,m)
    {
        char ch;int x,y;
        cin>>ch>>x>>y;
        if(ch=='E')
        {
            fa[get(x+n)]=get(y); ///扩展并查集用来记录x的敌人 x的敌人与y则是朋友 同理如下
            fa[get(y+n)]=get(x);
        }
        if(ch=='F')
        {
            Merge(x,y);
        }
    }
    for(int i=1;i<=n;i++)
        if(fa[i]==i) tot++;
    cout<<tot;
    return 0;
}

昆虫配偶
此题关于昆虫的性别关系也是一道扩展并查集题目

#include<bits/stdc++.h>
using namespace std;
typedef long long ll ;
typedef pair<int,int> PII;
typedef pair<ll,ll> PLL;
#define debug() cout<<"fuck      "<<endl;
#define fr(i,k,n) for(int i=k;i<=n;i++)
#define fo(i,k,n) for(int i=n;i>=k;i--)
const int mod = 107,N=1e5+5,INF=0x3f3f3f3f;
const double eps=1e-6;
ll n,m,k,tot=0,fa[N],d[N],tx[N];
char s[5];
inline ll read()
{
    ll x=0,f=1; char ch=getchar();
    while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}
    while(ch>='0'&& ch<='9') {x=x*10+ch-'0';ch=getchar();}
    return f*x;
}
struct node
{
    int x,y;
    int s;
}a[N];
int get(int x)
{
    if(x==fa[x]) return x;
    int father=get(fa[x]);
    return fa[x]=father;
}
void Merge(int x,int y,int z)
{
    int fx=get(x),fy=get(y);
    fa[fx]=fy;
}
int main()
{
    int t=read(),cnt=0;
    while(t--)
    {
        n=read(),m=read();
        fr(i,1,2*n) fa[i]=i;
        int flag=0;
        fr(i,1,m)
        {
            int x=read(),y=read();
            if(get(x)==get(y))
                flag=1;
            else
            {
                fa[get(x+n)]=get(y);   ///扩展并查集 将y合并到与x不同性别的集合上
                fa[get(y+n)]=get(x);
            }
        }
        printf("Scenario #%d:\n",++cnt);
        if(flag)
          printf("Suspicious bugs found!\n\n");
        else
          printf("No suspicious bugs found!\n\n");
    }
    return 0;
}

P4047 [JSOI2010]部落划分
算法标签是二分+并查集+生成树 刚开始一直在想着怎么去二分 后来才发现答案是在两点之间距离中,果断去掉二分。按照最小生成树的思路直到已经有了m个集合,然后一直循环找到下一个不在同一集合点的距离即是答案。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll ;
typedef unsigned long long ull;
#define fr(i,n) for(int i=1;i<=n;i++)
const int mod = 107,N=1e3+5,INF=0x3f3f3f3f;
int n,m,cnt=0,t=0,fa[N],pos;
inline ll read()
{
    ll x=0,f=1; char ch=getchar();
    while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}
    while(ch>='0'&& ch<='9') {x=x*10+ch-'0';ch=getchar();}
    return f*x;
}
struct node
{
    double l,r;
}b[N];
struct nodeb
{
    int i,j;
    double dis;
    bool operator < (nodeb q)
    {
        return dis<q.dis;
    }
}c[N*N];
int get(int x)
{
    if(x==fa[x]) return x;
    return fa[x]=get(fa[x]);
}
double check()
{
   int sum=0,pos;
   fr(i,n) fa[i]=i;
   fr(i,cnt)
   {
           int fx=get(c[i].i),fy=get(c[i].j);
           if(fx!=fy)
            {
                fa[fx]=fy;
            }
            int sum=0;
            fr(i,n) if(fa[i]==i) sum++;
            if(sum==m)
            {
                i++;
                while(get(c[i].i)==get(c[i].j)) i++;
                return c[i].dis;
            }
   }
}
int main()
{
       n=read(),m=read();
       fr(i,n) cin>>b[i].l>>b[i].r;
       fr(i,n) for(int j=i+1;j<=n;j++)
       c[++cnt].dis=sqrt((b[i].l-b[j].l)*(b[i].l-b[j].l)+(b[i].r-b[j].r)*(b[i].r-b[j].r)),c[cnt].i=i,c[cnt].j=j;
       sort(c+1,c+cnt+1);
       printf("%.2f",check());
    return 0;
}

P5937 [CEOI1999]Parity Game
思路:由于n过大而m的大小合适,那么对n进行离散化操作。
例如:x1和x2的奇偶性相同 x2和x3奇偶性相同 则x1和x3奇偶性相同
x1和x2奇偶性不同 x2和x3奇偶性不同 则x1和x3奇偶性相同 类似于异或操作;令d[x]表示x到根节点路径上的异或
d[x]表示x到p d[y]表示y到p d[p]表示p到q的异或值 因此 ans=d[x]^ d[y]^d[p]

#include<bits/stdc++.h>
using namespace std;
typedef long long ll ;
#define fr(k,n) for(int i=k;i<=n;i++)
#define debug() cout<<"fuck"<<endl;
const int mod = 107,N=2e4+5,INF=0x3f3f3f3f;
int n,m,fa[N],d[N],a[N],tot=0;
char str[10];
inline int read()
{
    int x=0,f=1; char ch=getchar();
    while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}
    while(ch>='0'&& ch<='9') {x=x*10+ch-'0';ch=getchar();}
    return f*x;
}
struct node
{
    int l,r;
    int ans;
}query[N];
void Mywork()
{
    fr(1,m)
    {
        query[i].l=read(),query[i].r=read();
        scanf("%s",str);
        query[i].ans=(str[0]=='o'?1:0);
        a[++tot]=query[i].l-1;
        a[++tot]=query[i].r;
    }
    sort(a+1,a+tot+1);
    n=unique(a+1,a+tot+1)-a-1;  ///由于n过大 因此需要离散化
}
int get(int x)
{
     if(fa[x]==x) return x;
     int root=get(fa[x]);
     d[x]^=d[fa[x]];          ///d[x]表示x到根节点路径上的异或值  1表示x到根节点的奇偶性不相同 0表示相同
     return fa[x]=root;
}
int main()
{
  n=read(),m=read();
  Mywork();
  fr(1,n) fa[i]=i;
  fr(1,m)
  {
      int x=lower_bound(a+1,a+n+1,query[i].l-1)-a;
      int y=lower_bound(a+1,a+n+1,query[i].r)-a;
      int p=get(x),q=get(y);
      if(p==q)
       {
           if((d[x]^d[y])!=query[i].ans)
        {
            cout<<i-1<<endl;
            return 0;
        }
       }
      else
         fa[p]=q,d[p]=query[i].ans^d[x]^d[y];      ///d[x]表示x到p d[y]表示y到p d[p]表示p到q的异或值 因此 ans=d[x]^d[y]^d[p]
  }
  cout<<m<<endl;
  return 0;
}



P1197 [JSOI2008]星球大战
思路:利用逆向思维 从后往前进行操作 假设k个星球都已经被摧毁 求出此时的连通块数量 然后从后往前依次恢复被摧毁的星球 利用并查集求此时连通块数量 递推即可

#include<bits/stdc++.h>
using namespace std;
typedef long long ll ;
#define debug() cout<<"fuck   ";
#define fr(k,n) for(int i=k;i<=n;i++)
#define fo(k,n) for(int i=n;i>=k;i--)
const int mod = 107,N=4e5+5,INF=0x3f3f3f3f;
int n,m,k,tot=0,fa[N],good[N],ans[N],a[N];
int ver[2*N],head[N],Next[N*2];
inline ll read()
{
    ll x=0,f=1; char ch=getchar();
    while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}
    while(ch>='0'&& ch<='9') {x=x*10+ch-'0';ch=getchar();}
    return f*x;
}
void add(int x,int y)
{
     ver[++tot]=y,Next[tot]=head[x],head[x]=tot;
}
int get(int x)
{
    if(x==fa[x]) return x;
    return fa[x]=get(fa[x]);
}
void Merge(int x,int y)
{
    fa[get(x)]=get(y);
}
int main()
{
    n=read(),m=read();
    fr(1,m)
    {
        int x=read()+1,y=read()+1;
        add(x,y);add(y,x);     ///利用无向图记录边
    }
    fr(1,n) fa[i]=i;
    k=read();
    fr(1,k)
    {
        int x=read()+1;
        good[x]=1;
        a[i]=x;
    }
    int sum=n-k;///逆思维:从后往前推 假设k个星球已经摧毁 此时还有多少连通块
    fr(1,n)
    {
         if(good[i]) continue;
          for(int j=head[i];j;j=Next[j])
          {
              int y=ver[j];
              if(!good[y]&&get(i)!=get(y))
              {
                  sum--;
                  Merge(i,y);
              }

          }
    }
    ans[k+1]=sum;
    fo(1,k)
    {
        good[a[i]]=0;   ///恢复第i个星球求出此时连通块数
        sum++;
        for(int j=head[a[i]];j;j=Next[j])
        {
            int y=ver[j];
            if(!good[y]&&get(a[i])!=get(y))
            {
                  sum--;
                  Merge(a[i],y);
            }
        }
        ans[i]=sum;
    }
    fr(1,k+1) printf("%d\n",ans[i]);
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值