并查集·算法笔记

并查集

并查集我个人认为一种用来处理某些特殊数据结构的算法

这种算法有两个操作,合并查询

  • 合并:能够高时效的将某一些符合题目要求的数据合并在一个集合
  • 查询:能够高时效的查询某个指定数据是否存在于某个集合之中,或者是计算满足题目要求的集合数量

那么这个算法是如何运作的呢?

接下来我们看几道例题

  1. 亲戚:并查集的基本运行思路(点我跳转qaq)
  2. 黑社会团伙:反集补集思想(点我跳转qaq)

例题1.亲戚

Luogu传送门

这是一道十分简单的并查集入门题,这里用来讲述并查集的运作思路。

根据题意,我们可以得到如果B是A的亲戚,C是B的亲戚,那么ABC三个人互为亲戚,也就是说ABC三个节点可以被放置在同一个集合当中,为了方便表述,我们可以认为这是一棵树,A是根,B与C都是A的子节点。

并查集.png

由此,我们便可以利用并查集,将有亲戚关系的两个节点,进行挂靠,在查询时,只要利用一个递归,不停向上询问,直到问到自己的根节点,如根节点相同,便是有亲戚关系。

但是,还有一个问题,如此下来某个点都向上询问一遍,将会耗费大量的时间,那么何来高时效之说呢?

这里我们需要对并查集做一个优化,使其能更快的计算出自己的根节点,我称之为缩点,将所有有关系的点都进行直接挂靠,也就是说,构造出一颗深度为2的树,如图。

并查集优化.png

如此一来,在查询的时候就可以直接访问到自己的根节点了。

#include<stdio.h>
#include<algorithm>
int n,m,a,b,fa[10005];
int find(int x){   //查询函数
    if(fa[x]!=x) fa[x]=find(fa[x]);  //缩点:反复向上询问,询问到根节点才赋值
    return fa[x];
}
int main(){
    scanf("%d%d",&n,&m);
    for(register int i=1;i<=n;i++){fa[i]=i;} //初始化,开始时每个节点都是一个根节点
    for(register int i=1;i<=m;i++){
        scanf("%d%d",&a,&b);
        a=find(a);b=find(b);
        if(a!=b) fa[b]=fa[a];  //合并
    }
    int Q;
    scanf("%d",&Q);
    for(register int i=1;i<=Q;i++){
        scanf("%d%d",&a,&b);
        if(find(a)==find(b)) printf("Yes\n");  //根节点相同
        else printf("No\n");
    }
    return 0;
}

通过这道题目,我们可以了解了并查集的基本运行思路,其实很多题的基本代码也是依照这个作为模板,不过在此基础上作出一些变化。

例题2.黑社会团伙

Luogu传送门

由题意可知,这题与上一题大题框架一样,变化的地方在于

  • 求集合数量
  • 多出了新的概念,我敌人的敌人也是我的朋友

由新的概念便可以引申出许多新的关系,稍作整理,就有如下关系

  • 我朋友的朋友是我的朋友
  • 我朋友的敌人是我的敌人
  • 我敌人的敌人是我的朋友
  • 我敌人的朋友是我的敌人

未完待续......

猜你喜欢

转载自www.cnblogs.com/lightcoder/p/11418204.html
今日推荐