并查集超级易懂讲解
并查集的基本操作

并查集的基本操作并查集(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),即常数时间。
四、路径压缩路径压缩可以进一步优化查找操作的时间复杂度。
并查集

• 3.合并2个元素所在的集合(connect):按秩合并,简单的说就是
按照集合元素的多少。将元素少的集合合并到元素的集合中。
int father[6000]; //father[x]表示x的父节点 int rank[6000]; //rank[x]表示x的秩 /* 初始化集合*/ void unit(int n){ int i; for(i=1;i<=n;i++) father[i]=i,rank[i]=0; } /* 查找x元素所在的集合,回溯时压缩路径*/ int find(int a){ if(a!=father[a]) father[a]=find(father[a]); return father[a]; }
• 2.查找祖先(find):也就是查找一个元素所在的集合。判断2个元素是否属
于同一个集合,只要看他们所在集合的祖先是否相同;合并2个集合,就是使一个集合 的祖先成为另外一个集合的祖先。查找祖先时,通过路径压缩来简化。也就是,当我 们经过"递推"找到祖先节点后,"回溯"的时候顺便将它的子孙节点都直接指向祖先。
/* 按秩合并x,y所在的集合*/ void connect(int a,int b){ int x,y; x=find(a);y=find(b); if(x==y) return ; if(rank[x]>rank[y]) father[y]=x; else{ if(rank[x]==rank[y]) rank[y]++; father[x]=y; } }
NBU:家族
3题畅通工程;
POJ:1611 The Suspects
2524 Ubiquitous Religions 2560 Freckles;
并查集

加权合并启发式策略
• 最坏情况下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
路径压缩
并查集解决元素分组及连通性问题

并查集解决元素分组及连通性问题并查集(Disjoint-set Union)是一种常用的数据结构,主要用于解决元素分组及连通性问题。
它将n个元素划分为若干个不相交的集合,每个集合通过一个代表来标识。
在该数据结构下,我们可以高效地进行合并集合和查询某个元素所属的集合操作。
一、并查集的基本原理并查集主要由两个操作组成:合并(Union)和查找(Find)。
其中,合并操作将两个集合合并为一个集合,查找操作则用于确定某个元素所属的集合。
在并查集中,每个元素都有一个指向父节点的指针,初始时每个元素自成一个集合,其父节点指向自己。
合并操作就是将两个元素所在集合的根节点合并为一个根节点,即将其中一个集合的根节点指向另一个集合的根节点。
查找操作通过递归地向上查找父节点,最终找到根节点,从而确定该元素所属的集合。
二、并查集的具体实现并查集的具体实现可使用数组或树结构。
数组实现简单直观,树结构实现更加高效。
以下是树结构实现的代码示例:```pythonclass UnionFind:def __init__(self, n):self.parent = list(range(n))def union(self, a, b):self.parent[self.find(a)] = self.find(b)def find(self, x):if self.parent[x] != x:self.parent[x] = self.find(self.parent[x])return self.parent[x]```在这个实现中,parent数组记录了每个元素的父节点,初始时每个元素的父节点为自己。
合并操作通过将某个元素的根节点指向另一个元素的根节点来实现。
查找操作递归地向上查找父节点,直到找到根节点。
三、应用场景举例1. 图的连通性判断:利用并查集可以判断图中的两个节点是否连通。
假设图中有n个节点,我们可以用并查集来维护这些节点的连通情况。
并查集(全解)

并查集(全解)⾸先说并查集有三种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之间的距离⽐较,不相等的话,那么这句话就错了。
并查集__Union-Find_Sets_

对于并查集来说,每个集合用一棵树表示。 对于并查集来说,每个集合用一棵树表示。 集合中每个元素的元素名分别存放在树的结 点中,此外, 点中,此外,树的每一个结点还有一个指向其 双亲结点的指针。 双亲结点的指针。 为此,需要有两个映射: 为此,需要有两个映射: 集合元素到存放该元素名的树结点间的对 应; 集合名到表示该集合的树的根结点间的对 应。 设 S1= {0, 6, 7, 8 },S2= { 1, 4, 9 },S3= { 2, 3, , , 5}
O(∑ i ) = O( n )
2 i =1
n
Union操作的加权规则 Union操作的加权规则
为避免产生退化的树, 为避免产生退化的树,改进方法是先判断两集合中元素 的个数, 的个数,如果以 i 为根的树中的结点个数少于以 j 为根 的树中的结点个数, 的树中的结点个数,即parent[i] > parent[j],则让 j 成为 , i 的双亲,否则,让i成为 的双亲。 的双亲,否则, 成为 的双亲。 成为j的双亲 此即Union的加权规则。 此即 的加权规则
下标
0
123ຫໍສະໝຸດ 4567
8
9
parent - 1
4
-1 2
-1 2
0
0 0
4
集合S1, S2和 S3的双亲表示
S1 U S2的可能的表示方法
®
const int DefaultSize = 10; class UFSets { //并查集的类定义 并查集的类定义 public: UFSets ( int s = DefaultSize ); ~UFSets ( ) { delete [ ] parent; } const UFSets & operator = ( UFSets const & Value ); void Union ( int Root1, int Root2 ); int Find ( int x ); void UnionByHeight ( int Root1, int Root2 ); private: int *parent; int size; };
并查集算法详解

并查集算法详解并查集算法详解算法详解维护类型⾝为⼀个数据结构,我们的并查集,它的维护对象是我们的关注点.并查集适合维护具有⾮常强烈的传递性质,或者是连通集合性质.性质详解传递性质传递性,也就是具有传递效应的性质,⽐如说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)正在玩⼀个信息传递的游戏。
并查集——精选推荐

并查集并查集是⼀种树型的数据结构,⽤于处理⼀些不相交集合的合并及查询问题。
常常在使⽤中以森林来表⽰。
⼀,对并查集的认识并查集是树形结构,常常在题⽬中⽤来判断两个元素是否属于同⼀个集合,每个集合都有⼀个特征性元素称为这个集合的father,如果两个元素的father相同,则说明这两个元素属于同⼀集合,若这两个元素的father不相同,则说明这两个元素不属于⼀个集合。
并查集就是这样⼀种⽀持合并和查询的树形数据结构。
在题⽬中的应⽤有,判断两个点是否属于同⼀个联通块,最⼩⽣成树kruskal算法中,判断加边是否会有环等。
在考察并查集的题⽬中,⼀般我们⽤⼀个f数组来实现并查集的功能,起初所有的f[i]=i;证明所有元素都是单独⼀个集合。
//并查集初始化for(int i=1;i<=n;i++) f[i]=i;⼆,并查集的基本操作1,并查集的合并操作(merge)简⽽⾔之,就是将两个元素所在的集合合并成⼀个集合,操作简单,⾸先我们只需要找到两个集合的特征性元素,然后把其中⼀个特征性元素变成另⼀个,这样两个集合特征性元素相同,两个集合就合并在了⼀起,在merge操作中我们运⽤到了按秩合并来优化。
inline void merge(int f1,int f2){f[f1]=f2;return; }//普通合并按秩合并:所谓秩就是树⾼,按秩合并就是按照秩序合并,起初我们给每个点赋⾼为⼀,以后在每次合并的过程中,都⽤树⾼⼩的指向树⾼⼤的,这样可以保证树⾼不超过log2N,把查询的最坏复杂度从O(n)减⼩到O(logn)。
inline void merge(int x,int y){int f1=find(x);int f2=find(y);if(f1!=f2){if(rank[x]<=rank[y]) f[f1]=f2,rank[y]=max(rank[y],rank[x]+1);else f[f2]=f1,rank[x]=max(rank[x],rank[y]);}return;}2,并查集的查询操作就是查询两个元素是否属于同⼀个集合,⾸先我们要先找到这两个元素所在集合的特征性元素。
并查集详细讲解(转载)模板

并查集详细讲解(转载)模板小小的得意一下,本人做的第一个并查集完全是自己想象出来的方法,用后感觉效率不错。
后来准备认真学习“标准的”并查集时,发现就是我原来自创的那个方法。
并查集,就是Union-Find Set,也称不相交集合 (Disjoint Set)。
这在数据结构中本是很简单的一种东西。
当然,我是指算法的实现简单,而非其理论分析简单。
但我今天还是要扯一下这个,因为我突然发现我这几天写的并查集都是错的,今天做题时才发现。
所以还是把这个很简单的数据结构总结一下。
正如它的名字揭示的,并查集的基本操作有两个:Union和Find。
其中Find(x)返回元素x所在的集合的编号,Union(i,j)将i与j所在的集合合并,也就是说在此之后对它们中的每一个调用Find的返回值必须是一样的。
实现时采用父亲表示法的树结构,保证所有在一个集合里的元素都在一棵树内,集合的代表元素就是根。
初始时所有节点的父节点都是自身,查找时只须沿着向上的路径查找即可;如果要将两棵树合并只须将其中一棵的根的父节点设为另一棵树的根即可。
最主要的优化是所谓路径压缩。
简言之,就是在Find的实现时并不简单的return find(U[x]); 而是return U[x]=find(U[x]); 这样就将当前节点到根的路径上的所有节点的父亲都设为了根,将原本可能比较长的路径“压缩”了。
当然这是递归的实现,非递归的话需要沿路径走两次,第一次找到根,第二次再改变。
union操作更简单,只是U[find(x)]=find(y);一句而已。
我前两天写Tarjan算法时把这一句错写成U[x]=find(y),竟然也AC了……汗……不过今天做另一道需要并查集的题目就没那么幸运了。
路径压缩的并查集n个操作的序列的均摊复杂度为O(nα(n)),其中α是阿柯曼函数的一个反函数,增长极慢,在可以想象的n的范围内不超过5,故可视为常数。
并查集的一般用途就是用来维护某种具有自反、对称、传递性质的关系的等价类。
并查集

并查集(union-find sets)定义:并查集是一种树型的数据结构,用于处理一些不相交集合(Disjoint Sets)的合并及查询问题。
常常在使用中以森林来表示。
集就是让每个元素构成一个单元素的集合,也就是按一定顺序将属于同一组的元素所在的集合合并。
主要操作1.初始化把每个点所在集合初始化为其自身。
通常来说,这个步骤在每次使用该数据结构时只需要执行一次,无论何种实现方式,时间复杂度均为O(N)。
2.查找查找元素所在的集合,即根节点。
3.合并将两个元素所在的集合合并为一个集合。
通常来说,合并之前,应先判断两个元素是否属于同一集合,这可用上面的“查找”操作实现。
并查集是若干个不相交集合,能够实现较快的合并和判断元素所在集合的操作。
一般采取树形结构来存储并查集,并利用一个rank数组来存储集合的深度下界,在查找操作时进行路径压缩使后续的查找操作加速。
可以看成是将编号分别为1…N的N个对象划分为不相交集合,在每个集合中,选择其中某个元素代表所在集合。
并操作;把集合x集合y合并. 要求:x和y互不相交,否则不执行操作若x,y在同一集合,则说明有回路;查操作;搜索单元素x所在的集合,并返回该集合号。
第一种并查集的实现:constintN=1004;intUFSet[N];intn;//并查集中元素个数voidinit(intn){//从开始计数for(inti=1;i<=n;i++)UFSet[i]=i;}intfind(intx){//查集合号//时间复杂度Θ(1)returnUFSet[x];}voidmerge(intx,inty){//并操作//时间复杂度Θ(N)inta,b;a=min(UFSet[x],UFSet[y]);b=max(UFSet[x],UFSet[y]);for(intk=1;k<=n;k++)if(UFSet[k]==b)UFSet[k]=a;} >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 第二种并查集(树形结构实现)constintN=100002;typedefstruct{intparent;//记录其前驱intheight;//记录集合树高度}Node;NodeUFSet[N];voidinit(){//初始化操作//集合从开始for(inti=0;i<N;i++){UFSet[i].parent=i;UFSet[i].height=1;visited[i]=false;}}intfind(intx){//查,时间复杂度为Θ(N)while(x!=UFSet[x].parent)x=UFSet[x].parent;returnx;}voidmerge(intx,inty){//并,时间复杂度为Θ(1)x=find(x);y=find(y);//要求:x和y互不相交,否则不执行操作若x,y在同一集合,则说明有回路if(x==y)return;//合并操作应使树尽量平衡//即将深度小的树合并到深度大的树if(UFSet[x].height==UFSet[y].height){UFSet[y].parent=x;UFSet[x].height++;}elseif(UFSet[x].height>UFSet[y].height){UFSet[y].parent=x;}else{UFSet[x].parent=y;}} >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 对第二种并查集的优化(带路径压缩)//压缩路径有利于查找某个集合中的元素个数constintN= 10000002;typedefstruct{intparent;//记录前驱intcnt;//记录集合元素个数}Node;NodeUFSet[N];intn;intresult;voidinit(){for(inti=0;i<N;i++){UFSet[i].parent=i;UFSet[i].cnt=1;}}intfind(intx){inty=x,tmp;while(UFSet[y].parent!=y)y=UFSet[y].parent;while(x!=y){tmp=UFSet[x].parent;UFSet[x].parent=y;x=tmp;}returny;}voidmerge(intx,inty){if(UFSet[x].cnt>UFSet[y].cnt){UFSet[y].parent=x;UFSet[x].cnt+=UFSet[y].cnt;}else{UFSet[x].parent=y;UFSet[y].cnt+=UFSet[x].cnt;}}。
第14讲2_1并查集

4
6
2
3
5
6
6
4
6
2
河南理工大学ACM-ICPC培训
经典应用——最小生成树
5、算法过程示意:
6
2
1
5
5
4 2
6 5
1
5
5
4
5
1
3
1
3
3
5
6
6
原始图
4
6
2
3
5
6
6
4
6
2
河南理工大学ACM-ICPC培训
经典应用——最小生成树
5、算法过程示意:
6
2
1
5
5
4 2
6 5
1
5
5
4
5
1
3
1
3
3
5
6
6
原始图
河南理工大学ACM-ICPC培训
方法(1)——效率分析
find1(x) { return set[x]; }
Θ(1)
Merge1(a,b) { i = min(a,b); j = max(a,b); for (k=1; k<=N; k++) { if (set[k] == j) set[k] = i; } } Θ(N)
效果:任意顺序的合并操作以后,包含k个节点 的树的最大高度不超过 lg k
河南理工大学ACM-ICPC培训
优化后算法及效率
find2(x) { r = x; while (set[r] != r) r = set[r]; return r; } 最坏情况Θ(log N) merge3(a,b) { if (height(a) == height(b)) { height(a) = height(a) + 1; set[b] = a; } else if (height(a) < height(b)) set[a] = b; Θ(1) else set[b] = a; }
并查集

并查集所谓并查集,就是将一些有关系的东西合并起来,用于方便查找。
在此我也不讲太多理论的东西(理论参看高级数据结构.pdf中并查集部分),还是讲讲实际应用吧。
顾名思义,并查集就是并着查的集合。
对他的操作有两种:查找操作:(查找某个元素所在的集合):查找操作要表示一个集合,就要加上一个标记,那些用什么来标记呢,就用他的第一个元素。
合并操作:(将两个集合合并):将所要和并的两个元素所在集合的标记节点合并。
接下来我们来看几道例题:家族描述 Description若某个家族人员过于庞大,要判断两个是否是亲戚,确实还很不容易,现在给出某个亲戚关系图,求任意给出的两个人是否具有亲戚关系。
规定:x和y是亲戚,y和z是亲戚,那么x和z也是亲戚。
如果x,y是亲戚,那么x的亲戚都是y的亲戚,y的亲戚也都是x的亲戚。
输入格式 Input Format第一行:三个整数n,m,p,(n<=5000,m<=5000,p<=5000),分别表示有n个人,m个亲戚关系,询问p对亲戚关系。
以下m行:每行两个数Mi,Mj,1<=Mi,Mj<=N,表示Ai和Bi具有亲戚关系。
接下来p行:每行两个数Pi,Pj,询问Pi和Pj是否具有亲戚关系。
输出格式 Output FormatP行,每行一个’Yes’或’No’。
表示第i个询问的答案为“具有”或“不具有”亲戚关系。
该题是标准并查集经典题目,只要找标记就行了。
程序如下:读入数据,将节点的标记记为他自己:vara:array[0..5000]of integer;i,j,k,m,n,p:integer;begin readln(n,m,p); for i:=1 to n do a[i]:=i; end.操作:function fa(x:integer):integer;beginif a[x]<>x thenbegin a[x]:=fa(a[x]);fa:=a[x]; endelse fa:=x; end;for i:=1 to m do begin readln(j,k); if fa(j)<>fa(k) thena[fa(k)]:=j; end;判断,写出结果:for i:=1 to p do begin readln(j,k); if fa(j)=fa(k) then writeln('Yes') else writeln('No'); end;团伙某城市里住着n个人,任何两个认识的人不是朋友就是敌人,而且满足:1.我朋友的朋友是我的朋友。
并查集

对于每个C i j命令,需要进行如下操作:
– –
对战舰i,j进行查找和路径压缩 判断是否有a[i]=a[j],如是则表示他们是同一队列,输出|b[i]b[j]|-1;如果不是则输出-1
银河英雄传说(NOI2002) 例一 银河英雄传说
关于战舰i的路径压缩的步骤:
–
–
从i出发寻找根root,并累计从i到根root的深度,得 到b[i] 再次遍历从i出发到root的路径.对于途中的每一个 节点k(假设在修改k之前a[k]=a1,b[k]=b1),则我 们根据已经修改过的战舰k的a,b值对战舰a1的a, b值进行修改,即:
银河英雄传说(NOI2002) 例一 银河英雄传说
然而,老谋深算的莱因哈特早已在战略上取得了 主动.在交战中,他可以通过庞大的情报网络随时监 听杨威利的舰队调动指令. 在杨威利发布指令调动舰队的同时,莱因哈特为 了及时了解当前杨威利的战舰分布情况,也会发出一 些询问指令:C i j.该指令意思是,询问电脑,杨威 利的第i号战舰与第j号战舰当前是否在同一列中,如果 在同一列中,那么它们之间布置有多少战舰. 作为一个资深的高级程序设计员,你被要求编写 程序分析杨威利的指令,以及回答莱因哈特的询问.
银河英雄传说(NOI2002) 例一 银河英雄传说
观察这个题目,开始时每条战舰作为独立的队 列.随着M命令的发布,分离的队列不断合并. 而且途中我们需要知道一些元素(战舰)的信 息 这些性质启发我们应用并查集的数据结构解决
银河英雄传说(NOI2002) 例一 银河英雄传说
由于我们要得到的信息除了某条战舰在哪个队 列,还要知道它在该队列中的位置.因此需要 对并查集进行扩充. 定义:
银河英雄传说(NOI2002) 例一 银河英雄传说
并查集知识讲稿1

并查集知识讲稿(并查集是若干个不相交集合,能够实现较快的合并和判断元素所在集合的操作,应用很多。
一般采取树形结构来存储并查集,并利用一个rank数组来存储集合的深度下界,在查找操作时进行路径压缩使后续的查找操作加速。
这样优化实现的并查集,空间复杂度为O(N),建立一个集合的时间复杂度为O(1),N次合并M查找的时间复杂度为O(M Alpha(N)),这里Alpha 是Ackerman函数的某个反函数,在很大的范围内(人类目前观测到的宇宙范围估算有10的80次方个原子,这小于前面所说的范围)这个函数的值可以看成是不大于4的,所以并查集的操作可以看作是线性的。
)算法复杂度是(e(loge+logn))。
在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中。
解决这类需要将集合进行若干次合并、查找的问题,人们发明了一种专门的数据结构——并查集。
我们从一个典型例子(上次我们讨论的问题)入手来讲述并查集的一些知识:亲戚(Relations)【问题描述】若某个家族人员过于庞大,要判断两个是否是亲戚,确实还很不容易,现在给出某个亲戚关系图,求任意给出的两个人是否具有亲戚关系。
规定:x和y是亲戚,y和z是亲戚,那么x和z也是亲戚。
如果x,y是亲戚,那么x的亲戚都是y的亲戚,y的亲戚也都是x的亲戚。
【输入文件】输入文件Relations.in第一行:三个整数n,m,Q(n<=20000,m<=1000000,q<=1000000),分别表示有n个人,m个亲戚关系,询问q对亲戚关系。
以下m行:每行两个数Mi,Mj,1<=Mi,Mj<=N,表示Mi和Mi具有亲戚关系。
接下来q行:每行两个数Qi,Qj,询问Qi和Qj是否具有亲戚关系。
【输出文件】输出文件Relations.out 共Q行,每行一个’Yes’或’No’。
并查集应用及优化技巧讲解

并查集应用及优化技巧讲解一、引言并查集是一种常见的数据结构,用于解决在一组元素中动态连接性问题。
它可以高效地合并集合和查询元素所在的集合,具有广泛的应用领域。
本文将介绍并查集的基本原理、应用场景以及一些优化技巧。
二、并查集的基本原理并查集由一个数组和一些合并集合和查询集合的操作构成。
数组的每个元素对应一个集合,通过数组的值来表示元素属于哪个集合。
合并操作可以将两个不同的集合合并成一个,而查询操作可以判断两个元素是否属于同一个集合。
三、并查集的应用场景1. 网络连通性问题:在网络中,判断两个主机是否能够通信可以使用并查集。
首先将各个主机看作一个个集合,当两个主机之间建立连接时,就将它们所在的集合合并成一个集合。
通过查询两个主机是否属于同一个集合,可以判断它们是否能够通信。
2. 社交网络:在社交网络中,判断两个人是否是朋友可以使用并查集。
首先将每个人看作一个个集合,当两个人成为朋友时,就将它们所在的集合合并成一个集合。
通过查询两个人是否属于同一个集合,可以判断他们是否是朋友。
3. 图的连通性问题:在图论中,判断两个节点是否连通可以使用并查集。
首先将每个节点看作一个个集合,当两个节点之间有边相连时,就将它们所在的集合合并成一个集合。
通过查询两个节点是否属于同一个集合,可以判断它们是否连通。
四、基本实现下面是并查集的基本实现:```pythonclass UnionFind:def __init__(self, n):self.parent = [i for i in range(n)] # 初始化每个元素的父节点为自身def find(self, x):if self.parent[x] != x:self.parent[x] = self.find(self.parent[x]) # 路径压缩return self.parent[x]def union(self, x, y):root_x = self.find(x)root_y = self.find(y)if root_x != root_y:self.parent[root_x] = root_y```五、路径压缩优化在并查集的基本实现中,find操作中的路径压缩可以进一步优化。
算法:并查集

算法:并查集 由于⽀持这两种操作,⼀个不相交集也常被称为联合-查找数据结构(其他的重要⽅法,MakeSet,⽤于建⽴单元素集合。
有了这些⽅法,许多经典的划分问题可以被解决。
为了更加精确的定义这些⽅法,需要定义如何表⽰集合合。
接着,Find(x) 返回 x 所属集合的代表,⽽ Union 使⽤两个集合的代表作为参数说明:左边是A,笔误! 上图中简单演⽰了并查集的两个操作,⼀个是FIND,⼀个UNION。
并查集(树)优化后的MakeSet和Union伪代码:function MakeSet(x)x.parent := xx.rank := 0function Union(x, y)xRoot := Find(x)yRoot := Find(y)if xRoot == yRootreturn这⼉是Find:function Find(x)if x.parent != xx.parent := Find(x.parent)return x.parent 这两种⽅法的优势互补,同时使⽤⼆者的程序每个操作的平均时间仅为,是增加的阿克曼函数。
因为是其的反函数,故在⼗分巨⼤时还是⼩于5。
因此,平均运⾏时间是⼀个极⼩的常数。
{int pu = Find(u);int pv = Find(v);if(pu==pv)return false;if (ranks_[pv] > ranks_[pu])parents_[pu] = pv;else if (ranks_[pu] > ranks_[pv])parents_[pv] = pu;else {parents_[pv] = pu;ranks_[pu] += 1;}return true;}public int Find(int u){while (parents_[u]!=u){parents_[u]=parents_[parents_[u]];u=parents_[u];}return u;}}主要操作合并两个不相交集合 操作很简单:先设置⼀个数组(阵列)Father[x],表⽰x的“⽗亲”的编号。
并查集及其应用PPT课件

2
并查集及其应用
输入样例(relation.in):
10 7
24
57
13
89
输出样例(relation.out):
12
Yes
56
No
23
Yes
3
34
7 10
89
3
பைடு நூலகம்
并查集及其应用
问题分析: 将每个人抽象成为一个点,数据给出M个边的关系,两个人
由上图可以看出,操作是在集合的基础上进行的,没有必要保 存所有的边,而且每一步得到的划分方式是动态的。
如何来实现以上的算法思想呢?我们就用到并查集。
8
并查集及其应用
二、并查集的基本思想
1、什么叫并查集
并查集(union-find set)是一种用于分离集合操作的抽象 数据类型。它所处理的是“集合”之间的关系,即动态地维护 和处理集合元素之间复杂的关系,当给出两个元素的一个无序 对(a,b)时,需要快速“合并”a和b分别所在的集合,这其间 需要反复“查找”某元素所在的集合。“并”、“查”和“集” 三字由此而来。在这种数据类型中,n个不同的元素被分为若 干组。每组是一个集合,这种集合叫做分离集合(disjoint set)。并查集支持查找一个元素所属的集合以及两个元素各 自所属的集合的合并。
并查集及其应用
一、引例 例一、 亲戚(relation) 问题描述: 或许你并不知道,你的某个朋友是你的亲戚。他可能是你的曾祖父的
外公的女婿的外甥女的表姐的孙子!!! 如果能得到完整的家谱,判断两个人是否亲戚应该是可行的,但如果
两个人的最近公共祖先与他们相隔好几代,使得家谱十分庞大,那么检 验亲戚关系实非人力所能及。在这种情况下,最好的帮手就是计算机。 为了将问题简化,你将得到一些亲戚关系的信息,如Marry和Tom是亲 戚,Tom和Ben是亲戚,等等。从这些信息中,你可以推出Marry和 Ben是亲戚。
集训队讲座第十讲并查集的深化与扩展课件

并查集的路径压缩技巧
01
路径压缩技巧是一种优化技术,用于减少并查集的查
找时间。
02
路径压缩通过在查找过程中将经过的节点直接连接到
根节点,从而减少后续查找时需要遍历的节点数目。
03
路径压缩技巧可以有效地降低并查集的查找时间复杂
度,提高算法效率。
03
并查集的扩展应用
动态并查集
动态并查集是在常规并查 集的基础上,增加了动态 更新和查询的功能。
的边界冲突。
连通性问题
03
在图论中,可以使用并查集来判断两个节点是否连通。
02
并查集的深入理解
并查集的数据结构
并查集是一种用于处理一些不相交集合(Disjoint Sets)问题的数据结构 。
并查集的基本元素是节点,每个节点代表一个集合,通过指针连接表示元 素之间的关系。
并查集通常使用树形结构表示,其中根节点表示集合的代表元素,其他节 点表示属于该集合的元素。
区间并查集
将元素和区间相关联,实现区间的合并和查询 操作。
树形并查集
将并查集与树形数据结构相结合,实现树形结构的合并和查询操作。
并查集与其他数据结构的结合使用
并查集与堆
将并查集与堆相结合,实现优先级队列的操 作。
并查集与图
将并查集与图相结合,实现图的连通性查询 和最短路径查询。
并查集与线段树
将并查集与线段树相结合,实现区间查询和 更新操作。
并查集可以用于社交网络分析,通过将社交网络中的节点和边进行合并
和查询,可以快速地找到社交网络中的连通分量、社区结构等。
02
社交网络中的连通分量
并查集可以快速地找到社交网络中的连通分量,即网络中相互连接的节
并查集

并查集--学习详解文章作者:yx_th000文章来源:Cherish_yimi (/cherish_yimi/) 转载请注明,谢谢合作。
昨天和今天学习了并查集和trie树,并练习了三道入门题目,理解更为深刻,觉得有必要总结一下,这其中的内容定义之类的是取自网络,操作的说明解释及程序的注释部分为个人理解。
并查集学习:●并查集:(union-find sets)一种简单的用途广泛的集合. 并查集是若干个不相交集合,能够实现较快的合并和判断元素所在集合的操作,应用很多,如其求无向图的连通分量个数等。
最完美的应用当属:实现Kruskar算法求最小生成树。
●并查集的精髓(即它的三种操作,结合实现代码模板进行理解):1、Make_Set(x) 把每一个元素初始化为一个集合初始化后每一个元素的父亲节点是它本身,每一个元素的祖先节点也是它本身(也可以根据情况而变)。
2、Find_Set(x) 查找一个元素所在的集合查找一个元素所在的集合,其精髓是找到这个元素所在集合的祖先!这个才是并查集判断和合并的最终依据。
判断两个元素是否属于同一集合,只要看他们所在集合的祖先是否相同即可。
合并两个集合,也是使一个集合的祖先成为另一个集合的祖先,具体见示意图3、Union(x,y) 合并x,y所在的两个集合合并两个不相交集合操作很简单:利用Find_Set找到其中两个集合的祖先,将一个集合的祖先指向另一个集合的祖先。
如图●并查集的优化1、Find_Set(x)时路径压缩寻找祖先时我们一般采用递归查找,但是当元素很多亦或是整棵树变为一条链时,每次Find_Set(x)都是O(n)的复杂度,有没有办法减小这个复杂度呢?答案是肯定的,这就是路径压缩,即当我们经过"递推"找到祖先节点后,"回溯"的时候顺便将它的子孙节点都直接指向祖先,这样以后再次Find_Set(x)时复杂度就变成O(1)了,如下图所示;可见,路径压缩方便了以后的查找。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
高级数据结构设计--并查集及实现学习笔记(有趣篇)并查集的程序设计:为了解释并查集的原理,我将举一个更有趣的例子。
话说江湖上散落着各式各样的大侠,有上千个之多。
他们没有什么正当职业,整天背着剑在外面走来走去,碰到和自己不是一路人的,就免不了要打一架。
但大侠们有一个优点就是讲义气,绝对不打自己的朋友。
而且他们信奉“朋友的朋友就是我的朋友”,只要是能通过朋友关系串联起来的,不管拐了多少个弯,都认为是自己人。
这样一来,江湖上就形成了一个一个的群落,通过两两之间的朋友关系串联起来。
而不在同一个群落的人,无论如何都无法通过朋友关系连起来,于是就可以放心往死了打。
但是两个原本互不相识的人,如何判断是否属于一个朋友圈呢?我们可以在每个朋友圈内推举出一个比较有名望的人,作为该圈子的代表人物,这样,每个圈子就可以这样命名“齐达内朋友之队”“罗纳尔多朋友之队”……两人只要互相对一下自己的队长是不是同一个人,就可以确定敌友关系了。
但是还有问题啊,大侠们只知道自己直接的朋友是谁,很多人压根就不认识队长,要判断自己的队长是谁,只能漫无目的的通过朋友的朋友关系问下去:“你是不是队长?你是不是队长?”这样一来,队长面子上挂不住了,而且效率太低,还有可能陷入无限循环中。
于是队长下令,重新组队。
队内所有人实行分等级制度,形成树状结构,我队长就是根节点,下面分别是二级队员、三级队员。
每个人只要记住自己的上级是谁就行了。
遇到判断敌友的时候,只要一层层向上问,直到最高层,就可以在短时间内确定队长是谁了。
由于我们关心的只是两个人之间是否连通,至于他们是如何连通的,以及每个圈子内部的结构是怎样的,甚至队长是谁,并不重要。
所以我们可以放任队长随意重新组队,只要不搞错敌友关系就好了。
于是,门派产生了。
下面我们来看并查集的实现。
int pre[1000];这个数组,记录了每个大侠的上级是谁。
大侠们从1或者0开始编号(依据题意而定),pre[15]=3就表示15号大侠的上级是3号大侠。
如果一个人的上级就是他自己,那说明他就是掌门人了,查找到此为止。
也有孤家寡人自成一派的,比如欧阳锋,那么他的上级就是他自己。
每个人都只认自己的上级。
比如胡青牛同学只知道自己的上级是杨左使。
张无忌是谁?不认识!要想知道自己的掌门是谁,只能一级级查上去。
find这个函数就是找掌门用的,意义再清楚不过了(路径压缩算法先不论,后面再说)。
再来看看join函数,就是在两个点之间连一条线,这样一来,原先它们所在的两个板块的所有点就都可以互通了。
这在图上很好办,画条线就行了。
但我们现在是用并查集来描述武林中的状况的,一共只有一个pre[]数组,该如何实现呢?还是举江湖的例子,假设现在武林中的形势如图所示。
虚竹小和尚与周芷若MM是我非常喜欢的两个人物,他们的终极boss分别是玄慈方丈和灭绝师太,那明显就是两个阵营了。
我不希望他们互相打架,就对他俩说:“你们两位拉拉勾,做好朋友吧。
”他们看在我的面子上,同意了。
这一同意可非同小可,整个少林和峨眉派的人就不能打架了。
这么重大的变化,可如何实现呀,要改动多少地方?其实非常简单,我对玄慈方丈说:“大师,麻烦你把你的上级改为灭绝师太吧。
这样一来,两派原先的所有人员的终极boss 都是师太,那还打个球啊!反正我们关心的只是连通性,门派内部的结构不要紧的。
”玄慈一听肯定火大了:“我靠,凭什么是我变成她手下呀,怎么不反过来?我抗议!”抗议无效,上天安排的,最大。
反正谁加入谁效果是一样的,我就随手指定了一个。
这段函数的意思很明白了吧?再来看看路径压缩算法。
建立门派的过程是用join函数两个人两个人地连接起来的,谁当谁的手下完全随机。
最后的树状结构会变成什么胎唇样,我也完全无法预计,一字长蛇阵也有可能。
这样查找的效率就会比较低下。
最理想的情况就是所有人的直接上级都是掌门,一共就两级结构,只要找一次就找到掌门了。
哪怕不能完全做到,也最好尽量接近。
这样就产生了路径压缩算法。
设想这样一个场景:两个互不相识的大侠碰面了,想知道能不能揍。
于是赶紧打电话问自己的上级:“你是不是掌门?”上级说:“我不是呀,我的上级是谁谁谁,你问问他看看。
”一路问下去,原来两人的最终boss都是东厂曹公公。
“哎呀呀,原来是记己人,西礼西礼,在下三营六组白面葫芦娃!”“幸会幸会,在下九营十八组仙子狗尾巴花!”两人高高兴兴地手拉手喝酒去了。
“等等等等,两位同学请留步,还有事情没完成呢!”我叫住他俩。
“哦,对了,还要做路径压缩。
”两人醒悟。
白面葫芦娃打电话给他的上级六组长:“组长啊,我查过了,其习偶们的掌门是曹公公。
不如偶们一起及接拜在曹公公手下吧,省得级别太低,以后查找掌门麻环。
”“唔,有道理。
”白面葫芦娃接着打电话给刚才拜访过的三营长……仙子狗尾巴花也做了同样的事情。
这样,查询中所有涉及到的人物都聚集在曹公公的直接领导下。
每次查询都做了优化处理,所以整个门派树的层数都会维持在比较低的水平上。
路径压缩的代码,看得懂很好,看不懂也没关系,直接抄上用就行了。
总之它所实现的功能就是这么个意思。
提到并查集就不得不提并查集最经典的例子:食物链。
POJ 1182 食物链/JudgeOnline/problem?id=1182题目告诉有3种动物,互相吃与被吃,现在告诉你m句话,其中有真有假,叫你判断假的个数(如果前面没有与当前话冲突的,即认为其为真话)这题有几种做法,我以前的做法是每个集合(或者称为子树,说集合的编号相当于子树的根结点,一个概念)中的元素都各自分为A, B, C三类,在合并时更改根结点的种类,其他点相应更改偏移量。
但这种方法公式很难推,特别是偏移量很容易计算错误。
下面来介绍一种通用且易于理解的方法:首先,集合里的每个点我们都记录它与它这个集合(或者称为子树)的根结点的相对关系relation。
0表示它与根结点为同类,1表示它吃根结点,2表示它被根结点吃。
那么判断两个点a, b的关系,我们令p = Find(a), q = Find(b),即p, q分别为a, b子树的根结点。
1. 如果p != q,说明a, b暂时没有关系,那么关于他们的判断都是正确的,然后合并这两个子树。
这里是关键,如何合并两个子树使得合并后的新树能保证正确呢?这里我们规定只能p合并到q(刚才说过了,启发式合并的优化效果并不那么明显,如果我们用启发式合并,就要推出两个式子,而这个推式子是件比较累的活...所以一般我们都规定一个子树合到另一个子树)。
那么合并后,p的relation肯定要改变,那么改成多少呢?这里的方法就是找规律,列出部分可能的情况,就差不多能推出式子了。
这里式子为: tree[p].relation = (tree[b].relation - tree[a].relation + 2 + d) % 3; 这里的d为判断语句中a, b的关系。
还有个问题,我们是否需要遍历整个a子树并更新每个结点的状态呢?答案是不需要的,因为我们可以在Find()函数稍微修改,即结点x继承它的父亲(注意是前父亲,因为路径压缩后父亲就会改变),即它会继承到p 结点的改变,所以我们不需要每个都遍历过去更新。
2. 如果p = q,说明a, b之前已经有关系了。
那么我们就判断语句是否是对的,同样找规律推出式子。
即if ( (tree[b].relation + d + 2) % 3 != tree[a].relation ), 那么这句话就是错误的。
3. 再对Find()函数进行些修改,即在路径压缩前纪录前父亲是谁,然后路径压缩后,更新该点的状态(通过继承前父亲的状态,这时候前父亲的状态是已经更新的)。
核心的两个函数为:int Find(int x){int temp_p;if (tree[x].parent != x){// 因为路径压缩,该结点的与根结点的关系要更新(因为前面合并时可能还没来得及更新).temp_p = tree[x].parent;tree[x].parent = Find(tree[x].parent);// x与根结点的关系更新(因为根结点变了),此时的temp_p为它原来子树的根结点.tree[x].relation = (tree[x].relation + tree[temp_p].relation) % 3;}return tree[x].parent;}void Merge(int a, int b, int p, int q, int d){// 公式是找规律推出来的.tree[p].parent = q; // 这里的下标相同,都是tree[p].tree[p].relation = (tree[b].relation - tree[a].relation + 2 + d) % 3;}而这种纪录与根结点关系的方法,适用于几乎所有的并查集判断关系(至少我现在没遇到过不适用的情况…可能是自己做的还太少了…),所以向大家强烈推荐~~搞定了食物链这题,基本POJ上大部分基础并查集题目就可以顺秒了,这里仅列个题目编号: POJ 1308 1611 1703 1988 2236 2492 2524。
下面来讲解几道稍微提高点的题目:POJ 1456 Supermarket/JudgeOnline/problem?id=1456这道题贪心的思想很明显,不过O(n^2)的复杂度明显不行,我们可以用堆进行优化,这里讲下并查集的优化方法(很巧妙)。
我们把连续的被占用的区间看成一个集合(子树),它的根结点为这个区间左边第一个未被占用的区间。
先排序,然后每次判断Find(b[i])是否大于0,大于0说明左边还有未被占用的空间,则占用它,然后合并(b[i], Find(b[i]) –1)即可。
同样这里我们规定只能左边的子树合并到右边的子树(想想为什么~~)。
POJ 1733 Parity game/JudgeOnline/problem?id=1733这题同样用类似食物链的思想。
首先我们先离散化,因为原来的区间太大了(10^9),我们可以根据问题数目离散成(10^4)。
我们要理解,这里的离散化并不影响最终的结果,因为区间里1的奇偶个数与区间的大小无关(这句话有点奇怪,可以忽略...),然后每次输入a, b,我们把b++,如果他俩在一个集合内,那么区间[a, b]里1的个数相当于b.relation ^ a.relation,判断对错即可。
如果不在一个集合内,合并集合(这里我们规定根结点小的子树合并根结点大的,所以要根据不同情况推式子),修改子树的根结点的状态,子树的其他结点状态通过Find()函数来更新。
hdu 3038 How Many Answers Are Wrong/showproblem.php?pid=3038上面那题的加强版,不需要离散化,因为区间的和与区间的大小有关(和上面的那句话对比下,同样可以忽略之…),做法与上面那题差不多,只是式子变了,自己推推就搞定了。