并查集进阶
并查集的基本操作

并查集的基本操作并查集(Disjoint Set),也叫做不相交集合数据结构,用来解决一些集合的合并与查询问题。
本文将介绍并查集的基本操作,包括初始化、查找、合并等,以帮助读者更好地理解并应用该数据结构。
一、初始化在使用并查集之前,需要先进行初始化。
初始化并查集时,首先需要确定集合的个数,然后为每个集合分配一个代表元素。
并查集的代表元素是每个集合中的一个元素,用于标识该集合。
通常情况下,可以将每个元素初始化为其自身作为代表元素。
二、查找查找操作用于确定某个元素所属的集合。
在并查集中,每个元素都有一个对应的代表元素,通过查找操作可以找到某个元素所属的集合。
具体的查找操作可以通过递归或迭代实现。
其中,递归实现方法如下:1. 递归查找- 输入:元素x- 输出:x所在集合的代表元素- 查找操作递归实现示例代码:```pythondef find(x):if x == root[x]:return xelse:return find(root[x])```- 在递归查找操作中,判断元素x与x的代表元素root[x]是否相同,若相同则x为代表元素;若不相同,则递归查找root[x]的代表元素。
查找操作的时间复杂度为O(log*n),其中,log*n是一个较小的常数。
三、合并合并操作用于将两个不相交的集合合并为一个集合。
在并查集中,合并操作主要涉及两个代表元素的合并。
具体的合并操作可以通过将其中一个集合的代表元素指向另一个集合的代表元素实现。
合并操作的基本步骤如下:1. 合并操作- 输入:元素x和元素y所在的集合- 输出:合并后的集合- 合并操作示例代码:```pythondef union(x, y):root[x] = y```- 在合并操作中,将其中一个集合的代表元素root[x]指向另一个集合的代表元素y。
这样,两个集合就被合并成一个集合。
合并操作的时间复杂度为O(1),即常数时间。
四、路径压缩路径压缩可以进一步优化查找操作的时间复杂度。
并查集路径压缩优化方法讲解

并查集路径压缩优化方法讲解并查集(Disjoint Set Union)是一种用于解决连通性问题的数据结构,常用于判断图中两个节点是否属于同一个连通分量。
在并查集中,每个节点表示一个元素,通过合并节点来构建集合,实现快速的查找和合并操作。
1. 并查集基本原理并查集最初的实现方法是通过使用树来表示集合,其中每个节点通过指向父节点来建立树结构。
树的根节点表示集合的代表元素,每个节点的父节点指向它所属集合的代表元素。
2. 查找操作查找操作用于找到某个元素所属的集合,即找到该元素的代表元素。
从给定的元素开始,不断向上查找直到找到根节点,即代表元素。
代码示例:```int find(int[] parent, int x) {if (parent[x] == x) {return x;}parent[x] = find(parent, parent[x]); // 路径压缩return parent[x];}```这段代码中的`find`方法使用了递归来实现路径压缩。
路径压缩的核心思想是将查找路径上的每个节点直接指向根节点,从而减少后续的查找时间。
3. 合并操作合并操作用于将两个集合合并成一个,即将两个集合的根节点连接起来。
合并操作可以简单地将一个根节点的父节点指向另一个根节点,从而实现合并。
代码示例:```void union(int[] parent, int x, int y) {int rootX = find(parent, x);int rootY = find(parent, y);if (rootX != rootY) {parent[rootX] = rootY;}}```4. 路径压缩优化路径压缩优化通过将每个节点直接指向根节点,使得查找操作的路径更短。
在常规的实现中,每个节点的父节点都指向其根节点,但这会导致树的高度较高,进而影响查找操作的性能。
路径压缩优化在查找操作中,将经过的节点直接指向根节点,从而使得树的高度减少。
并查集

加权合并启发式策略
• 最坏情况下Union过程需要O(n)时间(可能 将一个较长的表合并到一个较短的表上, 需更新较长表中每个对象的指向代表的指 针) • 改进:每个表记录其长度,每个把较短的 表合并到较长的表上去
小技巧: 小的合并到大的中
• 显然, 把小的合并到大的中, 这样Union操作会比 较节省时间 • 用n, m, f分别表示Make-Set的次数, 总操作次数 和Find-Set的次数, 则有 • 定理: 所有Union的总时间为O(nlogn) • 推论: 所有时间为O(m + nlogn) • 证明: 单独考虑每个元素x, 设所在集合为Sx,则修 改rep[x]时, Sx至少加倍. 由于Sx不超过n, 因此修 改次数不超过logn, 更新n个元素总花费nlogn
增强型链结构
• 给每个结点增加一个指回rep的指针
• Make-Set(x): 仍为常数 • Find-Set(x): 降为常数(直接读rep) • Union(x, y): 变得复杂: 需要把Sy里所有元 素的rep指针设为rep[Sx]!
增强型链结构的合并
• 可以把x合并到y中,也可以把y合并在x中
}
LINK(x,y)
{
if rank[x] > rank[y] then p[y] = x else p[x] = y if rank[x] == rank[y] then rank[y] = rank[y] + 1
}
运行效率分析
• 同时使用按秩合并合路径压缩时,最坏情 况运行时间为O(ma(n)),其中a(n) 时一个增长极其缓慢的函数,在任何可想 象的不相交集合数据结构的应用中,都有a (n)<= 4
路径压缩
并查集(全解)

并查集(全解)⾸先说并查集有三种1、第⼀种是最简单的,没有权值2、第⼆种是带权值的并查集3、第三种是种类并查集(后⾯以⾷物链这道题来讲解)每⼀种都是有模板的,要尽可能理解后才能长时间记忆⼀、(first of all)所有并查集由三部分组成-------主函数、寻找根节点函数(finds)、合并根节点函数(join)、以上三种只是在这三各部分中有所不同finds函数int finds(int x){if(x!=v[x]){int y=finds(v[x]);v[x]=y;//有这⼀⾏的⽬的是剪断⼀条长链的现状,相当于让他们各⾃都直接指向他们最后的根节点,⽽不是跟着题意⼀个⼀个的连接起来(长链形状)return y;}return x;}join函数:void join(int x,int y,int z){int fx=finds(x);int fy=finds(y);if(fx!=fy)v[fx]=fy;//如果他们的根节点不⼀样,就要把他们连接起来(是连接他们的根节点⽽不是连接他们⾃⼰)//根节点相连接,就可以保证之前只想原根节点的都跟着更新了新的根节点}finds函数int finds(int x){if(x!=v[x]){int y=finds(v[x]);w[x]=(w[x]+w[v[x]]);//假如它1-->2的距离是3,2-->3的距离是3,那么1-->3的距离是不是等于3+3,这⼀⾏就是这个意思,此节点的权值//加上根节点的权值,就是此节点到根结点的权值v[x]=y;return y;}return x;}join函数:这⼀点就要涉及权值问题,在权值问题上⾯三⾓形是⽤来判断这⼀句话正确还是错误,四边形是⽤来判断在合并根节点时根节点之间的权值问题1、三⾓形是当要合并的两个点的根节点⼀样的时候⽤来判断正确与否(根节点⼀样,就不需要合并,要检查之前的操作是否与现在的冲突)此时的他们满⾜这个情况我们就是要判断这个现在题中给出的x到y之间的权值,与之前给出的x到其根节点与y到其根节点⽽推出来的x到y之间的距离⽐较,不相等的话,那么这句话就错了。
第十二章 并查集[15页]
![第十二章 并查集[15页]](https://img.taocdn.com/s3/m/df4161e0be23482fb5da4c56.png)
按树的结点个数合并 按树的高度合并 压缩元素的路径长度
按树结点个数合并 结点个数多的树的根结点作根
0 -1
1 -1
2 -1
3 -1
4 -1
5 -1
6 -1
2
0 Union(2, 0) 2
-5
-2
-7
1
3
51
30
2
2
0
222
4
6
3
3
4
65
3
30
按树高度合并 高度高的树的根结点作根
0 -0
0 S1 1 S2 2 S3
0 678
4 19
2 35
为简化讨论,忽略实际的集合名,仅用表示集 合的树的根来标识集合。
初始时, 用构造函数 UFSets(s) 构造一个森林, 每棵树只有一个结点, 表示集合中各元素自成一 个子集合。
用 Find(i) 寻找集合元素 i 的根。如果有两个 集合元素 i 和 j, Find(i) == Find(j), 表明 这两个元素在同一个集合中。
执行一次Union操作所需时间是 O(1),n-1次 Union操作所需时间是O(n)。
若再执行Find(0), Find(1), …, Find(n-1), 若被搜 索的元素为 i,完成 Find(i) 操作需要时间为O(i), 完成 n 次搜索需要的总时间将达到
n
O( i) O(n2) i 1
并查集支持以下三种操作:
Union (Root1, Root2) //并操作
Find (x)
//搜索操作
UFSets (s)
//构造函数
对于并查集来说,每个集合用一棵树表示。
为此,采用树的父亲数组表示作为集合存储表示。
并查集算法详解

并查集算法详解并查集算法详解算法详解维护类型⾝为⼀个数据结构,我们的并查集,它的维护对象是我们的关注点.并查集适合维护具有⾮常强烈的传递性质,或者是连通集合性质.性质详解传递性质传递性,也就是具有传递效应的性质,⽐如说A传递给B⼀个性质或者条件,让B同样拥有了这个性质或者条件,那么这就是我们所说的传递性.连通集合性质连通集合性,和数学概念上的集合定义是差不多的, ⽐如说A和B同属⼀个集合,B和C同属⼀个集合,那么A,B,C都属于同⼀个集合.这就是我们所谓的连通集合性质.算法步骤⼀般来说数据结构算法,没有所谓的算法步骤,但是却有半确定的模块功能.初始化操作数据结构的初始化,通常都是有⼀个固定的模块,并查集也不例外.对于并查集⽽⾔,它的初始化,就是指向的⽗亲节点.我们可以想象集合就是⼀个⼩圈⼦,⽽没⼀个⼩圈⼦都得有⼀个圈主,那么显然所以⼈都是围绕着圈主⾏动的.⽐如说Acwing这个⼤圈⼦中,yxc 总裁就是我们的红太阳,圈主⼤⼈.同属于⼀个集合的⼈们,显然每⼀个⼈的指向⽬标,显然都是这个圈⼦的圈主.然⽽刚开始的时候,显然Acwing的成员们,在没有加⼊Acwing的时候,基本上都是素不相识的.因此呢,我们所有⼈肯定是都是属于⾃⼰的⼀个单⼈⼩圈⼦.⾃⼰显然就是⾃⼰这个⼩圈⼦的圈主.综上所述,我们刚开始,每⼀个⼈的指向数组,也就是father数组,肯定都是指向⾃⼰.合并操作两个⼈最远的距离,是沉默,⽽Acwing这个⼤家庭,让你我们更加亲近.海内存知⼰,天涯若⽐邻,⽹络世界的发展,Acwing⽹站的建⽴,沟通了⾝为程序员的你我他.现在你成为了Acwing的⼀员,⽽⼩A同学也成为了Acwing的⼀员.显然通过Acwing这个充满爱的⼤家庭,使得你和⼩A同学产⽣了联系,因此现在你和⼩A同学同属于⼀个名为Acwing的集合.因为你和⼩A同学,需要建⽴⼀种联系,让全世界都知道,你和⼩A同学都来⾃富有爱⼼的⽹站Acwing⼤家庭,所以我们就需要⽤合并操作.⼀个⼈的标签,就是⼀个⼈的指向数组,既然你想和⼩A同学缔结关系的话,那么你和⼩A同学的指向数组就需要开始变化了.⼩A同学是Acwing的⾦牌元⽼,他的指⽰数组就是Acwing,那么⾝为新成员的你需要修改⾃⼰的指向数组,指向⼩A的同学.说明你和⼩A同学存在着上下级关系.路径压缩Acwing是⼀个充满温情的⽹站,上下级这种关系显然⾮常的不友好,那么我们不得不需要斩断这种关系.你指向着⼩A同学,⼩A同学指向着Acwing.这个⼤圈⼦的名字就叫做Acwing,显然⼩A同学和你同属于Acwing⼤圈⼦.为了让上下级关系消失,我们不得不改变我们的集合指向⽅式.我们发现,如果说我们让所有Acwing成员,都指向Acwing这个⼤家庭的话,那么显然我们的上下级关系消失了,取⽽代之的则是我们的⼈⼈平等,互帮互助的友善关系.也就是我们的Acwing精神主旨之⼀.Acwing精神不仅仅使得⼈与⼈之间更加友好,⽽且⼤⼤提⾼了我们的⼯作效率.⽐如说如果说N个⼈,他们之间的关系统统都是上下级关系的话,那么显然我们的⼯作效率会⼤⼤降低.假如说同学6想要告诉Acwing⽹站的yxc总裁,⼀个地⽅有改进优化的建议,那么他需要不停地往上传递信息,效率是O(n)但是如果我们按照⼈⼈平等,互帮互助的Acwing精神主旨之⼀,来进⾏编排的话,那么显然效率会乘坐⽕箭,⼤⼤提⾼.此时我们发现提出⼀个建议的效率,会⼤⼤提⾼,我们⾮常完美的处理,让效率成为了O(1)题⽬选讲第⼀题题⽬描述有n个同学(编号为 1 到n)正在玩⼀个信息传递的游戏。
并查集快速合并和查找
并查集快速合并和查找并查集是一种用于处理集合合并和查找的数据结构,它支持在近乎常数时间内进行元素的合并和查找操作。
并查集广泛应用于图论、网络连接问题、社交网络分析等领域。
在本文中,我们将介绍并查集的原理、实现及其应用。
一、原理并查集主要由两个基本操作组成:合并和查找。
合并操作将两个不相交的集合合并为一个集合,而查找操作则用于确定元素所属集合的代表元素。
在并查集中,每个集合都由一个代表元素来表示。
初始时,每个元素都是一个单独的集合,且以自身作为代表元素。
当两个集合需要合并时,需要将其中一个集合的代表元素指向另一个集合的代表元素。
这样,两个集合就合并为一个了。
查找操作则用于确定元素所属的集合。
它通过递归地查找每个元素的父节点,直到找到代表元素为止。
通过路径压缩的优化技巧,可以使得查找操作的时间复杂度接近常数级别。
二、实现并查集的基本实现可以使用数组来表示。
其中,数组的下标表示元素的编号,数组中的每个元素存储了该元素的父节点编号。
初始化时,每个元素的父节点都设为自身:```for i in range(n):parent[i] = i```合并操作可以通过将两个元素的代表元素相连来实现:```def union(x, y):x_parent = find(x)y_parent = find(y)parent[x_parent] = y_parent```查找操作则需要递归地查找每个元素的父节点,并进行路径压缩:```def find(x):if parent[x] != x:parent[x] = find(parent[x])return parent[x]```三、应用并查集在许多领域都有广泛的应用。
1. 图论在图论中,可以使用并查集来判断两个节点是否属于同一个连通分量,从而解决连通性问题。
例如,可以用并查集来判断无向图中是否存在环。
2. 网络连接问题在网络连接问题中,可以使用并查集来判断两个节点之间是否存在网络连接。
并查集支持三种操作
◆并查集支持三种操作:Init(X): 集合初始化:把元素xi加到集合Si中。
每个集合Si只有一个独立的元素xi,并且元素xi就是集合Si的代表元素。
Find(x): 查找:查找xi所在集合Si的代表root[Si]。
优化:路径压缩。
Union(x, y): 合并:把x和y所在的两个不同集合合并。
1)、x=x (自反性)2)、如果x=y,则y=x (对称性)3)、如果x=y,y=z,则x=z (传递性)采用树型结构实现并查集的基本思想是:◆每个子集合用一棵树来表示。
树中的每个结点用于存放集合中的一个元素。
◆树中的每个结点x设置一个指向父亲的指针。
father[x]◆用根结点的元素代表该树所表示的集合。
►Init(X): 集合初始化:father[xi]=0(或者xi); 每个结点都是一颗独立的树,是该树的代表元素。
►Find(x): 查找:查找x所在集合Si的代表root[Si]。
即:查找x所在树的树根结点(代表元素)。
顺着x往上找,直到找到根节点,也就确定了x所在的集合。
►Union(x, y): 合并x和y所在的不同集合。
p=find(x) ;q=find(y);if p<>q then father[p]=q 或father[q]=pfunction find(i:longint):longint;{非递归算法找i的根}var j,k,t:longint;beginj:=i;//顺着结点i开始向上找,直到根为止。
while a[j]<>0 do j:=a[j];find:=j;end;function find(i:longint):longint;//递归算法找i的根var j,k,t:longint;beginif a[i]=0 then exit(i); //若i为根,返回本身结点序号find:=find(a[i]); //否则继续向上找end;function find(i:longint):longint;{非递归算法找i的根}var j,k,t:longint;beginj:=i;while a[j]<>0 do j:=a[j]; // 顺着i向上找根。
并查集整理
并查集相关总结并查集:(union-find sets)是一种简单的用途广泛的集合. 并查集是若干个不相交集合,能够实现较快的合并和判断元素所在集合的操作,应用很多,如其求无向图的连通分量个数、最小公共祖先、带限制的作业排序,还有最完美的应用:实现Kruskar算法求最小生成树。
其实,这一部分《算法导论》讲的很精炼(所有知识都很精炼)。
一般采取树形结构来存储并查集,在合并操作时可以利用树的节点数(加权规则)或者利用一个rank数组来存储集合的深度下界--启发式函数,在查找操作时进行路径压缩使后续的查找操作加速。
这样优化实现的并查集,空间复杂度为O(N),建立一个集合的时间复杂度为O(1),N次合并M查找的时间复杂度为O(M Alpha(N)),这里Alpha是Ackerman函数的某个反函数,在很大的范围内这个函数的值可以看成是不大于4的,所以并查集的操作可以看作是线性的。
它支持以下三种操作:-Union (Root1, Root2) //合并操作;把子集合Root2和子集合Root1合并.要求:Root1和Root2互不相交,否则不执行操作.-Find (x) //搜索操作;搜索单元素x所在的集合,并返回该集合的名字--根节点标示.-UFSets (s) //构造函数。
将并查集中s个元素初始化为s个只有一个单元素的子集合.-对于并查集来说,每个集合用一棵树表示。
-集合中每个元素的元素名分别存放在树的结点中,此外,树的每一个结点还有一个指向其双亲结点的指针。
-为简化讨论,忽略实际的集合名,仅用表示集合的树的根来标识集合。
-为此,采用树的双亲表示作为集合存储表示。
集合元素的编号从0到n-1。
其中n 是最大元素个数。
在双亲表示中,第i 个数组元素代表包含集合元素i 的树结点。
根结点的双亲为-m,m表示集合中的元素个数。
为了区别双亲指针信息( ≥ 0 ),集合元素个数信息用负数表示。
const int DefaultSize = 10;class UFSets{ //并查集的类定义private:int *parent;int size;public:UFSets ( int s = DefaultSize );~UFSets ( ) { delete [ ] parent; }UFSets & operator = ( UFSets const & Value );//集合赋值void Union ( int Root1, int Root2 );int Find ( int x );void UnionByHeight ( int Root1, int Root2 );};UFSets::UFSets ( int s ){ //构造函数size = s;parent = new int [size+1];for ( int i = 0; i <= size; i++ )parent[i] = -1;}unsigned int UFSets::Find ( int x ){ //搜索操作,最好int p = x;while( parent[p] > 0 )p = parent[p];while( x != p ){//路径压缩int temp = parent[x];parent[x] = p;x = temp;}return x;}void UFSets::Union ( int Root1, int Root2 ){ //并parent[Root2] = Root1; //Root2指向Root1}Find和Union操作性能不好。
并查集探索并查集的应用与优化
并查集探索并查集的应用与优化在计算机科学中,数据结构是一种组织和存储数据的方式,它可以通过一系列的操作来操作和访问数据。
并查集(Disjoint Set)作为一种基本的数据结构,被广泛应用于解决不相交集合的合并和查询问题。
本文将探讨并查集的应用以及一些优化技巧。
一、并查集的基本操作并查集由一组不相交的集合组成,每个集合称为一个"集合",每个集合有一个唯一的代表,可以是集合中的任意一个元素。
并查集主要支持以下操作:1. 初始化:初始时,每个元素都初始化为一个独立的集合,且每个集合代表为自己。
2. 查找:给定一个元素,返回该元素所在集合的代表。
3. 合并:将两个集合合并为一个集合,即将其中一个集合的代表指向另一个集合的代表。
二、并查集的应用1. 集合的合并与查询:并查集最基本的应用就是解决一些集合的合并与查询问题。
通过合并操作,可以将两个不相交的集合合并为一个集合;通过查询操作,可以判断两个元素是否属于同一个集合。
2. 网络连通性判断:在网络通信中,常常需要判断两个计算机是否可以相互通信。
可以将每个计算机作为一个节点,使用并查集来维护计算机之间的连接关系。
如果两个计算机属于同一个集合,说明它们可以相互通信;如果不属于同一个集合,则无法通信。
3. 连通图的判断:对于一个无向图,可以使用并查集来判断图是否连通。
将每个节点作为一个元素,初始时每个元素都是一个独立的集合,然后通过遍历图的边,不断合并集合。
最终如果图中只存在一个集合,则图是连通的;否则图是不连通的。
三、并查集的优化1. 基于Rank的合并优化:为了避免合并操作时的不平衡情况,可以使用"基于Rank的合并优化"。
通过维护每个集合的"秩",即集合中元素的数量,合并时将秩较小的集合合并到秩较大的集合下,从而保持合并后的集合平衡。
2. 路径压缩优化:在查找操作时,为了降低查找的时间复杂度,可以进行"路径压缩优化"。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
• 这个题目虽然是维护集合的合并,但元素是有序排列的,看起来又不符合集合的性质。这时候就 需要用到边带权了。
秀下代码
• void find(int x)
•{
•
if (f[x]==x) return x;
•
return find(f[x]);
•}
• void hebing(int x,int y)
•{
•
int X=find(x),Y=find(y);
•
if (large[X]>large[Y])
•
{
tarjan陪伴强联通分量 生成树完成后思路才闪光 欧拉跑过的七桥古塘 让你 心驰神往
键盘微凉 鼠标微凉 指尖流淌 代码千行 凸包周长 直径多长 一进考场 全都忘光
No.1
前置知识
并查集基础,路径压缩
前置知识:并查集
• 并查集是一个维护一些元素的集合归属的高级数据结构,具有时空双高效 的特点。
• 并查集的基本思想是将给出的同属关系定向后单向合并,用一个数组维护 各个元素所属集合的代表元素。
• 我们把每个动物分裂成3个节点,分别保存同类的集合,捕食对象的集合,和天敌的集合。如果两个动物是 同类,那就把他们同类、捕食对象和天敌的集合分别合并。如果A吃B,根据上文的环状结构,我们能推出 应该把A的捕食对象和B的同类,B的捕食对象和A的天敌,B的天敌和A的同类合并,然后就和普通并查集 一模一样了。
•
a[f1(x)]=f1(y);
•
c=f1(c);
•}
• int main()
•{
•
scanf("%d%d%d",&n,&m,&k);
•
i f ( b ==c) p r i n tf("%s \n","Ye s"); e l s e
printf("%s\n","No");
•
}
•
return 0;
•}
路径压缩
主体部分
03 扩展域或边带权,或两者兼而
有之
开胃菜
02 按秩合并
学习心得
04 如何学习一个算法¿
要求
• 避免出错 • 讲清算法 • 结合例题 • 结合代码 • 有所拓展 • 实际应用 • 学习心得
提示
• 洛 咕 日 爆id87 • 如果有意见可以学生端举手,我会记下来,下课后统一回复 • 这边强烈建议听课认真点) • 留点情啊
• 代码
• 双倍经验口答题
e.g.P2024食物链
• 有三种动物,他们互相捕食,构成环状结构。现在有n个动物,有m个命题,表示其中两个动物是同种动物 或捕食与天敌的关系。现在要输出这些命题中假命题的个数。1≤n≤5*104,1≤m≤105。
• 这题有一些很有趣的性质。首先,动物只有三种,只有5*104个动物。5*104*3=1.5*105,不会炸。其次, 三种动物之间构成了一个环,这说明如果A吃B,B吃C,那么C吃A(知道两个就能推出第三个关系),这 使得这题的推导十分方便。然而,我们不可能用一个位置同时存下三种关系(同类,捕食,天敌),这就要 用到扩展域思想。
出我们的按秩合并。 • 这题题解说: • 维护一个按秩合并的并查集,给连边操作加时间戳,查询的时候暴力求路径上时间
戳最大值
No.3
扩展域,边带权
小
拓
展
\
x
y
x
我可线队
们持段列
俯久树进 身留区出 欣下间图 赏的修上
迹改的
象求方
出向
总
量
边带权、扩展域简要思想
• 边带权,顾名思义,就是给并查集连边的时候,给每条边赋上 一个边权,在合并的时候用边权修改下节点的权值,然后用点 权来推导某些性质。
• 其实按秩合并并没有很大的实际用处,也没有多少题去考它。但是如果把它和路径压缩放 在一起优化的话,你就能跑出理论上的魔鬼复杂度O(n·α(depth)),这玩意绝对炸不了。
• 顺便说一句,按秩合并应该是玄学复杂度,理论上大约介于不加优化和路径压缩之间,所 以有一定的风险(但谁来卡这么一个又奇怪又没人用的算法呀)
• 具体实现是,我们用另一个数组存下每个节点的点权,在合并 和路径压缩的时候用边权和另一个节点的点权更新该点点权。
• 而扩展域的基本思想就是,把一个节点拆成k个子节点,每个子 节点存原节点的一个性质,分别存储互不干扰,同时把总数组 开大k倍,然后实现一些关系错综复杂的操作。
e.g.P1196银河英雄传说
• 最后,我想强调的是,学习信息竞赛是一个快乐和痛苦的过程,一切都要依靠自己的意 志。如果在机房里颓废了,那比在教室里睡觉还可怕。因此,我们一定要有极强的自律 能力,在算法的海洋中直面风雨,渐行渐远,学海无涯苦作舟。
如果标算太难请坚定信念 以那暴力模拟向正解吊唁
不如回头再看一眼题面 蒟蒻的蜕变 神犇出现 终将与Au擦肩
前置知识:路径压缩
• 上面那个代码是并查集最基本的写法,时间复杂度是—— • 活学活用:CSP2019提高第一轮 27题 • (一道题难倒英雄好汉啊) • 如果你觉得这个代码很慢,那是因为这个代码就是很慢。 • 所以我们需要一些逆天优化,这就是路径压缩。 • 写成代码就是这样: • void find(int x) { if (f[x]==x) return x;else return f[x]=find(f[x]);} • 也就只改了一个小地方嘛,但是小小的细节就能带来大大的优化。 • 它的复杂度变成了O(n·log2n)
No.2
开胃菜
按
秩
合
并
你在OJ上提交了千百遍 却依然不能卡进那时限 双手敲尽代码也敲尽岁月 只有我一人 写的题解 凋零在OJ里面
开胃菜:按秩合并
• 路径压缩想必是各个巨佬们都会的优化吧,然而它在带来高效的同时,有一个小小的缺憾, 那就是他没有严格记录输入中各个结点的合并关系,这既是它的优点,又是它的缺点。
• 我的爸爸的爸爸不是我的爸爸!
• 如果题目里硬要我们维护这个关系,那路径压缩就是一个废柴,我们就得请出另一个优化: 按秩合并。
• 按秩合并是基于一个很容易想到的贪心优化:在我们把两个集合(其实是两棵树)合并的 时候,让小(矮)的那棵树合并到大(高)的那棵树上,叫他爸爸,这样我们每次得到的集 合树都是深度最小的,寻找一个节点的最大复杂度log2(depth)也就越小。
• 这样,如果一个点x和另一个点y之间有奇数边,它就会连接到y2上,而我 们统计的是1系列的节点。要想让这条边影响结果,只能在y2和z1之间再 有一条边——根据上文性质,这也是条奇数边。因此这样连出来的边就能 保证1系列之间的互相连边都是偶数边。然后并查集跑下连通性就好了。
是不是有这种感觉:
No.4
• 它既在CCF系列赛制中被经常单独考察,又能融入其他的算法(比如 Kruskal),提高他们的效率,因此用途广泛,涵盖从普及到NOI+的大部 分比赛。
• 并查集的核心代码:
• void find(int x) { if (f[x]==x) return x;else return find(f[x]);} • 另外,使用并查集之前一定要把数组的初值f[i]归为i!
• 另外,这题也可以使用边带权并查集,请大家思考一下,如何设置边带权并查集的边权。
• 设点权、边权为0代表同类,1代表捕食,2代表天敌。合并a和b的时候,让点权num[f[a]]=(num[b]num[a]+side[x]+3)%3(边权),路径压缩的时候,树根的点权等于子节点所有点权之和%3(一定要%3! 不然点权就不在012之间了)(略)
•
f[Y]=X;
•
large[X]=max(large[X],large[Y]+
1);
•
}
•
else
•
{
•
f[X]=Y;
•
large[Y]=max(large[Y],large[X]+
1);
•
}
• }//large数组初始化为1
拓展题 BZOJ4668 冷战
• 给定 n 个点的图。动态的往图中加边,并且询问某两个点最早什么时候联通,强制在线。 • 由于这题要用到复杂的在线图状结构,故不能用路径压缩随便简化,所以我们就可以拿
织平思屏
成面想幕
忧的在在 伤向那深 的量虚夜 网交树微
错路微
生径发
长上亮
彷
徨
并查集进阶
13th May
NOIP2020
SPFA 01
笑
剪
颜背 枝
目 录 页 洋
溢 脸 庞
包 装 下 了 忧
告 诉 我 前 途
剪 去 我 们 的
伤 在 疯C o n t e n t s
何狂
方
前置知识
01 并查集思想和基础实现,
•
______①______
•}
• void f2(int x,int y)
•{
•
•
•
•
if (a[x]!=x) a[x]=f1(a[x]); •
return a[x];
•
•
•
•
for (i=1;i<=m;i++) {
scanf("%d%d",&b,&c); f2(b,c); } for (i=1;i<=k;i++) { scanf("%d%d",&b,&c); b=f1(b);