浅谈并查集 By cellur925【内含题目食物链、银河英雄传说等】

什么是并查集?

合并!查询!集合!

专业点说?

动态维护若干不重叠的和,支持合并查询的数据结构!(lyd老师说的)

数据结构特点:代表元。即为每个集合选择一个固定的元素,作为整个集合的代表,利用树形结构存储,每个节点都是一个元素,树根是集合的代表元素。(还是lyd老师说的)

两大基本操作

一、合并(merge())

即把两个集合合并到一个的操作。通俗的说,即令其中一个树根为另一个树根的子节点。

void merge(int x,int y)
{
       fa[getf(x)]=getf(y);
}

二、查询(getf())

朴素的查询效率太低,这里不再赘述。我们通常用到的高效方法是“路径压缩”(按秩合并由于在大多OI竞赛中并不必要,这里不再介绍)。

关于路径压缩,一图可以见真相。

也就是每次在查询节点在集合内祖先时,不能直接调用f[x],因为他是x的一个非根本祖先。而是调用getf函数通过迭代求解,在迭代的过程中,顺便完成了路径压缩。之后要提到的带权并查集、种类并查集与普通并查集的区别在getf上都有很大体系。可以说这一操作是并查集的核心。

这个操作的均摊复杂度为O(logN)。

int getf(int x)
{
       if(x==fa[x]) return x;
       return fa[x]=getf(fa[x]);
}

例题1 NOI2015程序自动分析

先要吐槽一句...这个题我自5月18号(大概)至今,已提交近30次,自己写的常数太丑,改了一次又一次,今天终于A了,所以...人活着还是要有梦想的嘛qwq。

是比较裸的并查集了,相等就进行合并操作,但注意本题数据离散程度较大,需要进行离散化。

献上我的卡线代码。

code

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<algorithm>
 4 using namespace std;
 5 typedef long long ll;
 6 int t;
 7 int fa[200005];
 8 int n,cnt,a[200005],tot,b[200005];
 9 struct taojun{
10     int p,q,op;
11 }node[200005];
12 void re(int &x)
13 {
14     x=0;
15     char ch=getchar();
16     bool flag=false;
17     while(ch<‘0‘||ch>‘9‘) flag|=(ch==‘-‘),ch=getchar();
18     while(ch>=‘0‘&&ch<=‘9‘) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
19     x=flag ? -x : x;
20 }
21 int getf(int x)
22 {
23     if(fa[x]==x) return x;
24     else return fa[x]=getf(fa[x]);
25 }
26 void merge(int k,int h)
27 {
28     fa[getf(k)]=getf(h);
29 }
30 void discrete()
31 {
32     sort(a+1,a+cnt+1);
33     tot=unique(a+1,a+cnt+1)-a-1;
34 }
35 int query(int x)
36 {
37     return lower_bound(a+1,a+tot+1,x)-a;
38 }
39 void analyauto()
40 {
41     n=0,cnt=0,tot=0;
42     re(n);
43     for(int i=1;i<=n;i++)
44     {
45         int x=0,y=0,z=0;
46         re(x),re(y),re(z);
47         node[i].p=x,node[i].q=y,node[i].op=z;
48         a[++cnt]=x,a[++cnt]=y;
49     }
50     discrete();
51     for(int i=1;i<=n;i++)
52     {
53         node[i].p=query(node[i].p);
54         node[i].q=query(node[i].q);
55     }
56     for(int i=1;i<=tot;i++) fa[i]=i;
57     for(int i=1;i<=n;i++)
58         if(node[i].op==1) merge(node[i].p,node[i].q);
59     for(int i=1;i<=n;i++)
60     {
61         if(node[i].op==0)
62         {
63             if(getf(node[i].p)==getf(node[i].q))
64             {
65                 printf("NO\n");
66                 return;
67             }
68         }
69     }
70     printf("YES\n");
71 }
72 int main()
73 {
74     re(t);
75     while(t--)
76      analyauto();
77     return 0;
78  } 

小结:并查集,在一张无向图中维护节点之间的连通性比较优秀,擅长动态维护许多有传递性的关系。

(还是lyd老师说的)

例题2 NOI2002银河英雄传说

LVYOUYW:神舟是哪年发射的来着?03?杨威利这个名字吼啊,莫非CCF提前一年已经知道了航天员杨利伟的名字?

2333...杨威利是真的存在的日本动漫《银河英雄传说》的主角...

学长您不会被打嘛...

扯淡结束

如果说并查集只是记录了集合的位置关系,那么在一些有权值的题目背景下,仅一个普通并查集维护关系貌似不太够用,我们还另需要一些数组来记录权值,本题就是这样。

我们可以维护一个数组d,用d[x]保存节点x到祖先节点fa[x]之间的边权。在路径压缩的同时,我们可以同时更新信息。

再用一个size[]在每个树根上记录集合大小,就能O(1)地进行查询。

code

 1 #include<cstdio>
 2 #include<algorithm>
 3 #include<cmath>
 4
 5 using namespace std;
 6
 7 int T;
 8 int fa[40000],d[40000],size[40000];
 9 char ch;
10
11 int getf(int p)
12 {
13     if(p==fa[p]) return p;
14     int root=getf(fa[p]);
15     d[p]+=d[fa[p]];
16     return fa[p]=root;
17 }
18
19 void merge(int p,int q)
20 {
21     int pp=getf(p);
22     int qq=getf(q);
23     fa[pp]=qq;
24     d[pp]=size[qq];
25     size[qq]+=size[pp];
26 }
27
28 int main()
29 {
30     for(int i=1;i<=30005;i++) fa[i]=i,size[i]=1;
31     scanf("%d",&T);
32     ch=getchar();ch=getchar();
33     while(T--)
34     {
35         int x=0,y=0;
36         ch=getchar();
37         if(ch==‘M‘)
38         {
39             scanf("%d%d",&x,&y);
40             merge(x,y);
41         }
42         if(ch==‘C‘)
43         {
44             scanf("%d%d",&x,&y);
45             if(getf(x)!=getf(y))
46             {
47                 printf("-1\n");
48                 ch=getchar();ch=getchar();
49                 continue;
50             }
51         //    printf("%d %d\n",d[x],d[y]);
52         //    printf("%d\n",abs(d[x]-d[y]));
53             printf("%d\n",abs(d[x]-d[y])-1);
54         }
55         ch=getchar();ch=getchar();
56     }
57     return 0;
58 }

例题3 NOI2001 食物链

在我刚学并查集的时候,我用裸并查集骗了20分......

这种并查集,被lyd老师称为‘扩展域’,被学长称作“种类并查集”

当时他说这个偏移量的设计真是 妙 啊!

关于种类并查集,引用一位大神的话:“和基础并查集有很大一部分相同, 多了一个判断2个元素是否属于同一个集团(不是集合, 集合是用来判断2个元素是否能够判断他们属不属于同一个集团:有点绕, 举个例子, 假如知道1和2在不同的集团, 3和4在不同集团,我们就不能判断1和3是否属于一个集团,而集合是用来判断他们是否在同一个集团假如:已知1和2在不同集合,2和3在不同集合, 那么我们就知道1和3在同一个集合);”  @Sky_sys,讲的很透彻

因为我在luogu看题解的时候,没有一个人跳出来解释原始的并查集作用是什么,看的我一脸mengbi。这下可是懂了。

于是,我们用一个数组flag[i]记录,表示i的祖先到i的偏移量,每次读入一句话,先看话中涉及的两动物是否在同一集合内,只有在同一集合内,才有可能继续判断;如果不在,我们先把它合并,假设当前这句话是对的。

合并的时候怎么搞?我们还是上一张图。

路径压缩的处理那里自己xjb推一下就好。记得取模!

code

 1 #include<cstdio>
 2 #include<algorithm>
 3
 4 using namespace std;
 5
 6 int n,k,ans;
 7 int fa[50090],flag[50090];
 8
 9 int getf(int x)
10 {
11     if(x==fa[x]) return x;
12     int tmp=fa[x];
13     fa[x]=getf(fa[x]);
14     flag[x]=(flag[tmp]+flag[x])%3;
15     return fa[x];
16 }
17
18 int main()
19 {
20     scanf("%d%d",&n,&k);
21     for(int i=1;i<=n;i++) fa[i]=i;
22     while(k--)
23     {
24         int ops=0,a=0,b=0;
25         scanf("%d%d%d",&ops,&a,&b);
26         if(a>n||b>n)
27         {
28             ans++;
29             continue;
30         }
31         if(ops==2&&a==b)
32         {
33             ans++;
34             continue;
35         }
36         int p=getf(a);
37         int q=getf(b);
38         if(p==q)
39         {
40             if((flag[b]-flag[a]+3)%3==ops-1) continue;
41             else ans++;
42         }
43         else//merge
44         {
45             flag[q]=(3+ops-1+flag[a]-flag[b])%3;
46             fa[q]=p;
47         }
48     }
49     printf("%d",ans);
50     return 0;
51 }

本文就要结束了,可是对于并查集,本蒟感觉还没有更深入理解qwq。所以过几天再刷几道题吧!qwq

原文地址:https://www.cnblogs.com/nopartyfoucaodong/p/9404524.html

时间: 2024-10-01 19:56:53

浅谈并查集 By cellur925【内含题目食物链、银河英雄传说等】的相关文章

浅谈java类集框架和数据结构(2)

继续上一篇浅谈java类集框架和数据结构(1)的内容 上一篇博文简介了java类集框架几大常见集合框架,这一篇博文主要分析一些接口特性以及性能优化. 一:List接口 List是最常见的数据结构了,主要有最重要的三种实现:ArrayList,Vector,LinkedList,三种List均来自AbstracList的实现,而AbstracList直接实现了List接口,并拓展自AbstractCollection. 在三种实现中,ArrayList和Vector使用了数组实现,可以认为这两个是

浅谈并查基

所谓并查集,就是一种支持并与查的数据结构,是一种集合.并即为合并,查即为查询. 我们给每个集合选择一个代表元素,每个元素记录一个\(fa\)数组,表示它所在的集合的代表元素.合并两个集合,我们只需要将其中一个集合的代表元素的\(fa\)数组赋值为另外一个集合的代表元素即可.代表元素的\(fa\)就是自己.这样子操作的话,所有元素之间的关系就会形成一个森林,若要查询两个元素是否在同一个集合内,只需要看看他们的根是否相同即可. 但是,如果树的深度为\(n\),每次查询都是\(O(n)\)的,这个复杂

浅谈高性能数据库集群——读写分离

本文主要介绍高性能数据库集群读写分离相关理论,基本架构,涉及的复杂度问题以及常见解决方案. 1 读写分离概述 基本架构图: 2 适用场景 读写分离不是银弹,并不是一有性能问题就上读写分离,而是应该先优化,例如优化慢查询,调整不合理的业务逻辑,引入缓存查询等只有确定系统没有优化空间后才考虑读写分离集群 3 引入的系统复杂度问题 问题一 主从复制延迟 问题二 分配机制 如何将读写操作区分开来,然后访问不同的数据库服务器? 解决方案1 客户端程序代码封装实现 基本架构图 业界开源实现 Sharding

浅谈欧拉函数 By cellur925

1.某神犇Blog 学了三遍的 欧拉函数φ--DEADFISH7 2.我要做一些补充o(* ̄▽ ̄*)o $φ(1)=1$: 公式有两种形式,一种有太多除法,实际可能会慢些.通用 对于任意$n$>1,1~$n$中与$n$互质的数之和等于$n*φ(n)/2$. 是积性函数. $sigma(d|n) φ(d)=n$. 代码实现 1°:朴素的质因数分解顺便求出 void init_phi() { int ans=n; for(int i=2;i<=sqrt(n);i++) { if(n%i==0) {

并查集 (Union-Find Sets)及其应用

定义 并查集是一种树型的数据结构,用于处理一些不相交集合(Disjoint Sets)的合并及查询问题.常常在使用中以森林来表示. 集就是让每个元素构成一个单元素的集合,也就是按一定顺序将属于同一组的元素所在的集合合并. 主要操作 初始化 把每个点所在集合初始化为其自身. 通常来说,这个步骤在每次使用该数据结构时只需要执行一次,无论何种实现方式,时间复杂度均为O(N). 查找 查找元素所在的集合,即根节点. 合并 将两个元素所在的集合合并为一个集合. 通常来说,合并之前,应先判断两个元素是否属于

并查集详解 (转)

http://blog.csdn.net/dellaserss/article/details/7724401 我从CSDN转的文章,原文作者我也不懂是谁,文章写得真的是诙谐幽默,使得内容更容易理解了. 来看一个实例,杭电OJ 1232畅通工程 Problem Description 某省调查城镇交通状况,得到现有城镇道路统计表,表中列出了每条道路直接连通的城镇.省政府“畅通工程”的目标是使全省任何两个城镇间都可以实现交通(但不一定有直接的道路相连,只要互相间接通过道路可达即可).问最少还需要建

HDU 3749 Financial Crisis 经济危机(并查集,割点,双连通分量)

题意:给一个图n个点m条边(不一定连通),接下来又q个询问,询问两个点是为“不相连”,“仅有一条路径可达”,“有两条及以上的不同路径可达”三种情况中的哪一种.注:两条以上的路径指的是路径上的点连1个点也不重复. 思路:并查集+tarjan求割点. (1)情况一:先并查集处理,如果两个点从一开始就不连通,直接输出zero (2)情况二和情况三:两点既然连通,那么可能是只有1条路径,比如中间隔着一个割点:也可能有多条路径,比如在同一个双连通分量内.那么直接判断其是否在同一个双连通分量内即可,若在同一

数据结构_课程设计——并查集:检查网络

***************************************转载请注明出处:http://blog.csdn.net/lttree******************************************** 这两天做数据结构课程设计,因为以前做过ACM题,感觉还可以,不是很难呀 ~~~~并查集:检查网络~~~~ 题目要求: 给定一个计算机网络以及机器间的双向连线列表,每一条连线允许两端的计算机进行直接的文件传输,其他计算机间若存在一条连通路径,也可以进行间接的文件传

04-树5. File Transfer--并查集

对于一个集合常见的操作有:判断一个元素是否属于一个集合:合并两个集合等等.而并查集是处理一些不相交集合(Disjoint Sets)的合并及查询问题的有利工具. 并查集是利用树结构实现的.一个集合用一棵树来表示,而多个集合便是森林.并查集中的“并”是将两个集合合并即两棵树合并成一颗树:“查”是查找一个元素属于哪个集合,即查找一个节点属于哪棵树.思路如下: 查:通过节点找寻父节点,一直向上查找直到根节点,返回根节点,而根节点代表唯一的那棵树: 并:先查找到两个节点所在的树,如果在同一棵树中(即查找