浅谈并查集

并查集——树型数据结构,应用就是合并与查询
基本应用:初始化,查找祖先,合并以及判等
基础的并查集主要是板子,
初始化

点击查看代码 ``` void renew(int n){ for(int i=1;i<=n;i++){ f[i]=i;//因为此时没有信息所以自己就是自己根节点 } } ```
查询依靠递归实现:
点击查看代码 ``` int find(int x){ if(f[x]!=x){ f[x]=find(f[x]);//f[x]表明是x的根节点记录 }//这里递归查找f【x】的父节点直至出现自己是自己父节点的根节点 return f[x];//实现一个路径压缩,方便下次查询 } ```
合并则是依靠上面的函数实现,其原理是两个根节点联系,即为两个树型集合合并:
点击查看代码
void unit(int x,int y){
  int a=find(x);
  int b=find(y);//寻找根节点
  if(a!=b){
    f[a]=b;//这部分视情况决定谁是根
  }
}
判等,即判断两元素是否在同一集合,其原理是判断根节点是否相等即可:
点击查看代码
bool judge(int x,int y){
  int a=find(x);
  int b=find(y);
  if(a==b){
    return true;
  }
  else{
    return false;
  }
}

基础的操作就是这样,非常的基础,也非常的简单
然而,越基础的东西,往往越重要
提高操作1:
带权并查集:这里的并查集中,父节点与子节点关系不再是一维的“父与子”,而有更多的关系,因此难度会升高
先介绍例题:
NOI2001 食物链
此题首先介绍各类动物之间关系,但不是简单的“父与子”,在父子之间还存在“吃”,“被吃”与“同类”这些关系,是典型的带权并查集
带权并查集维护时需考虑路径压缩带来的权值转变
具体分析一下:定义0 X P 为x与父节点相同,1为X吃父节点,2为x被父节点吃
1 X Y为题中同类 2 X Y 为题中X吃Y;
题中d=1时,(d-1)=0,恰为同类;
d=2,2 X Y时,(d-1)=1,恰为x吃父节点,
d=2,2 Y X时,(d-1)=-1=2(mod 3),恰为x被父节点吃
所以上述关系可以转化为权值
当遇见不符合已有真话构建的关系的话,判假即可,而能够在判假前实现逻辑自洽的,判真即可
算法思路形成,剩下的就是路径压缩
这里递推一下即可:对于x,x父节点及x父节点的父节点(称为x祖父节点)(简写为x,xp和xg)
枚举3*3=9种可能的(0 1 2)关系,会得到结论:
x与其祖父节点之间的关系为:
xg=(x+xp)%3;
对于四层以上的调用,递推即可
但压缩以后的判断:需要还原式子:
d=2时:
3-(relation [y]+ 3 - relation[x] ) % 3 != 1
这时判假即可;
3-x为xg与x关系,这里其实是一个把x当做祖父的原理去求得已有的x与xp(即y)的一个关系
如果为2,那么判真;
p=1时显然,就判x与y的根节点关系是否一致即可
剩下的不同根节点时,由于无法确定,认为说真话并补充即可
因此,我们可以打出一个正确的算法

点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int a=52021;
int n,k;
int f[a],d[a];
int ans;
int find(int x){
    if(f[x]!=x){
        int t=f[x];
        f[x]=find(f[x]);
        d[x]=(d[x]+d[t])%3;
    }
    return f[x];
}
int main(){
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++){
        f[i]=i;
        d[i]=0;
    }
    int p,x,y;
    for(int i=1;i<=k;i++){
        scanf("%d%d%d",&p,&x,&y);
        if(x>n||y>n||(p==2&&x==y)){
            ans++;
            continue;
        }
        int a=find(x);
        int b=find(y);
        if(p==1){
            if(a==b){
                if(d[x]!=d[y]){
                    ans++;
                }
            }
            else{
                f[a]=b;
                d[a]=(d[y]-d[x]+3)%3;
            }
        }
        else{
            if(a==b){
                if(d[x]!=(d[y]+1)%3){
                    ans++;
                }
            }
            else{
                f[a]=b;
                d[a]=(d[y]-d[x]+4)%3;
            }
        }
    }
    cout<<ans;
    return 0;
}
NOI2002 银河英雄传说 带权并查集,维护当前节点到队首距离及差值,合并时维护新的距离即可
点击查看代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int MAXN=30005;
int read(){
    int rv=0,fh=1;
    char c=getchar();
    while(c<'0'||c>'9'){
        if(c=='-') fh=-1;
        c=getchar();
    }
    while(c>='0'&&c<='9'){
        rv=(rv<<1)+(rv<<3)+c-'0';
        c=getchar();
    }
    return rv*fh;
}
int fa[MAXN],dis[MAXN],t,num[MAXN];
int find(int x){
    if(x!=fa[x]){
        int k=fa[x];
        fa[x]=find(fa[x]);
        dis[x]+=dis[k];
        num[x]=num[fa[x]];
    }
    return fa[x];
}
void merge(int x,int y){
    int r1=find(x),r2=find(y);
    if(r1!=r2){
        fa[r1]=r2;dis[r1]=dis[r2]+num[r2];
        num[r2]+=num[r1];
        num[r1]=num[r2];
    }
}
int query(int a,int b){
    int r1=find(a),r2=find(b);
    if(r1!=r2){
        return -1;
    }else {
        return abs(dis[a]-dis[b])-1;
    }
}
int main(){
    t=read();
    for(int i=1;i<=MAXN;i++) {fa[i]=i;num[i]=1;}
    for(int i=1;i<=t;i++){
        char c;
        scanf(" %c ",&c);
        int a=read(),b=read();
        if(c=='M'){
            merge(a,b);
        }else {
            printf("%d\n",query(a,b));
        }
    }
    return 0;
}

提高操作2:
实现贪心:
贪心是一种基础的算法,而并查集是一种描述关系的数据结构
所以,描述关系并且显然可以用贪心思路解决的问题,可用并查集或带权并查集维护
克鲁斯卡尔算法实现最小生成树就依靠本思路
这里介绍NOIP的一道例题:
NOIP2010 关押罪犯
介绍不同罪犯的关系,考虑并查集;
这里是在问最小值,考虑之前的二分和贪心
很显然这里答案的得出和二分不沾边,考虑贪心
这里我们选择贪心策略是尽可能让冲突值最大的尽可能不在同一个监狱
维护一个并查集,当一对敌人在同一并查集时结束即可
证明正确性:
如果不如此做,那么必然使得安排时有一组更大的冲突值提前发生而再次调换至最优解

点击查看代码
#include <bits/stdc++.h>
using namespace std;
struct data{
    int x;
    int y;
    int z;
}f[100005];
int n,m,a[20005],b[20005];
inline bool cmp(data a,data b){
    return a.z>b.z;
}
inline int find(int x){
    if(a[x]==x){
        return x;
    }
    a[x]=find(a[x]);
    return a[x];
}
inline void ad(int x,int y){
    x=find(a[x]);
    y=find(a[y]);
    a[x]=y;
}
inline bool check(int x,int y){
    x=find(x);
    y=find(y);
    if(x==y){
        return true;
    }
    return false;
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        a[i]=i;
    }
    for(int i=1;i<=m;i++){
        scanf("%d%d%d",&f[i].x,&f[i].y,&f[i].z);
    }
    sort(f+1,f+m+1,cmp);
    for(int i=1;i<=m+1;i++){
        if(check(f[i].x,f[i].y)){
            printf("%d",f[i].z);
            break;
        }
        else{
            if(!b[f[i].x]){
                b[f[i].x]=f[i].y;
            }
            else{
                ad(b[f[i].x],f[i].y);
            }
            if(!b[f[i].y]){
                b[f[i].y]=f[i].x;
            }
            else{
                ad(b[f[i].y],f[i].x);
            }
        }
    }
    return 0;
}
操作3:最小环
posted @   2K22  阅读(43)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
点击右上角即可分享
微信分享提示