并查集小结

---------------------------------------------------------------------------------------------------by Milkor -----------------------------2015.7.12

int delta[maxn];
int f[maxn];
void init()
{
int i;
for(i=0;i<maxn;i++)
{
f[i] = 1;
//init delta
}
}
int find(int a)
{
if(f[a]==a){return a;}
int ta = find(f[a]);
//ta 为 root. f[a] 为直接父亲节点
//update the delta[a]
return f[a] = ta;
//路径压缩。
}

void bing(int a,int b)
{
int ta = find(a);
int tb = find(b);
if(ta!=tb) //不是同一个集合中
{
f[ta] = tb;
//update the dalta[ta]
}
}

以上是并查集的常见写法。(没有维护deep数组,把深度低root 连接到 深度高的root上。)

考虑几个并查集常见的操作以及问题。

操作1:统计当前产生的集合数目。
//假设已经出现了1~N的关系
for(i=1;i<=N;i++)
{
if(f[i] = i)
{
++cnt;
}
}
因为初始化f[i] = i.而root会保留 f[i] = i.统计集合个数其实就是在统计root个数。

操作2:判断是否成环。
//在加入a和b的时候,判断a和b 是否是同根的。如果是同根就是成环了。
bool bing(int a,int b)
{
int ta = find(a);
int tb = find(b);
if(ta!=tb)
{
f[ta] = tb;
return true;
}
else
{
return false;
}
}
//false 是成环了。true是没有成环。并且合并。

操作3:统计集合数目。
//维护一个cou数组。这个维护只要维护在根节点处。不用像种类并查集。要获得各个节点对之间的关系。
init : cou[i] = 1;
bing : cou[tb] += cou[ta];

操作4:实现并查集的删除节点的操作。
//维护一个id数组。对于要删除的节点。就把该节点的id指向没有使用的节点处。
/*
原理稍微解释一下:
假如我们访问一个节点是通过一个id数组去访问的。
一开始:init:id[i] = i.是和我们通常的是一样的。
输入a,b. 那合并操作就是bing(id[a],id[b]).
对于删除操作 a.那就是令id[a] = cnt++.
//其中cnt一开始赋值为n.n表示节点数值范围。这样cnt指向的就是一个没有使用的节点。

试想一下。如果此时再次输入a.是否就能访问到一个新的节点。并且曾经是a的孩子节点也全部都不受影响。并且也不再是a的孩子了。因为a已经是一个新的节点了。
*/

操作5:统计一个节点的合并次数。
提出这个是为了提出并查集维护节点信息的一个核心思想:依托根节点。
这个也是为了能比较好理解食物链之类的种类并查集做的铺垫。

考虑一下这样的问题。
一个并查集中有 ta - ab - ac - ad 这样四个元素。其中ta是根节点。
另外一个并查集中有 tb - bb - bc -bd 这样四个元素。其中tb是根节点。
现在要让f[ta] = tb. 让ta作为tb的孩子节点连接上去。
那么必定ta 集合中的元素都要增加 1 次移动次数。
那么如何做到集合中每个元素都增加 1 次移动次数呢?我们自然而然会想到整个集合的关系枢纽ta.
如果我们让move[ta] += 1.
并且当我们find的时候。再把根节点的信息更新到我们需要的孩子上。就能获得孩子节点的完整信息了。这有点像线段树里的lazy标志。

int find(int a)
{
if(f[a]==a){return a;}
ta = find(f[a]);
move[a] += move[f[a]];
// 是把信息一层一层传下去的。所以一定是 += move[f[a]].获得完整信息之后。再路径压缩是符合结果的。
// 如果你考虑一个问题就是如果我一直做find(a)操作。那么move[a]是否会发生变化呢?答案是不会的。这个问题就留给你自己考虑吧。
return f[a] = ta;
}

void bing(int a,int b)
{
int ta = find(a);
int tb = find(b);
if(ta!=tb){f[ta] = tb;move[fa]=1;}
//因为作为根节点只可能移动一次。所以只用更新=1.
}

操作6:关于delta[]数组的更新和维护。即所谓带权值的并查集
delta[i] 表示 i和ti的关系。// ti = find(i).
这里体现了并查集维护节点信息的一个核心思想:依托根节点。
因为题目要求的是我们能获得任意对节点的关系。而我们只要获得节点对各自的根节点的关系。
就能经过一系列转换来获得两者的关系。

重点:
维护delta[i]:
提一下种类并查集中的类向量运算:
一个问题:A和B是不同的。B和C是不同的。那么A和C是同类的。
这个问题是:Find them, Catch them 中的关系联系。

我们令X和Y是不同的为1。令X和Y是相同的为0。
我们看看对于这个问题这样的设法是否满足所谓的类向量运算
A->B 为不同为1. (在本问题中A->B和B->A是一样的(因为A和B不相同,与B和A是不相同是一个意思。同样A和B相同也是如此)。在这点上其实是不符合向量运算。对于这个问题我们下面再谈)
B->C 为不同为1.
那么A->C?.
A->C = A->B + B->C = 1 + 1 = 2 对于这个我们对2取模。这是为了保证让我们的关系是有意义的同时。
其实也是为了让运算满足上述关系。
A->C = 0 是同类。所以满足我们的运算定义。
以上要获得A->C 的关系。可以类似向量中的绕一圈加法即A->C = A->A1 + A1->A2 + A2->A3 + A3... + An->C 称之为满足类向量加法。
如果有了这个。那么我们就可以很方便的做一个节点的delta的更新。

int find(int a)
{
if(f[a]==a){return a;}
int ta = find(a);
delta[a] = (delta[a] + delta[f[a]]) % 2;
return f[a] = ta;
}

对于这句delta[a] = (delta[a] + delta[f[a]]) % 2;
而这里的f[a] 其实是原来的根节点ta‘.
不然的话 delta[a] != (delta[a] + delta[f[a]]) % 2 。
画个图模拟一下并查集从无到有的创建过程就可以知道这点。
一开始
1->2
delta[1] 存储的是1->2 的关系信息。

1->2->3
2连接到3.
更新delta[2]的信息存储的是2->3的关系信息。而此时delta[1]始终还是1-2的。
所以delta[1] 更新为( delta[1] + delta[2] ) % 2.
当然你也可以多连接几次之后。然后再find 1 会发现1的信息更新的还是正确的。
原理同就是。
a->ta = a->ta‘ + ta‘->ta

bing函数。更新根节点(信息维护:依赖根节点)
void bing(int a,int b)
{
int ta = find(a);
int tb = find(b);
if(ta != tb)
{
f[ta] = tb;
delta[ta] = ( delta[a] + delta[b] + (a-b的关系) ) % 2;
}
}
delta[ta] = ( delta[a] + delta[b] + (a-b的关系) ) % 2;
理解为: ta->a + a->b + b->tb.
在本题中ta->a 实际意义上其实就是a->ta.

给出两个节点的关系判断:
此时如果两个节点在一个并查集里面并不是代表着这两个节点是同类关系。而是说这两个节点能够判断出关系。
要获得a->b的关系。
首先判断是否是同根。如果不是,说这两个节点还不能得出关系。
如果是:
我们可以通过a->b = a->t + t->b = a->t + b->t = (delta[a] + delta[b]) % 2 如果是0 那么就是相同。如果是1那么就是不同了。

考虑完这个问题。再考虑一个经典的食物链的问题。
问题核心是这样的:A吃B B吃C 那么C就吃A
我们该如何设值能够比较好地让我们进行关系的更新呢?如果设出来的值能过满足类向量加法运算是最最好不过了。
其实这是有的。
让 X吃Y 即 X->Y 为 1.
X和Y同类 就是 0.
Y吃X 即 X->Y 为 2.
这个时候你可以尝试验证一样是否满足类向量加法运算。答案当然是满足的。
并且比上题目有意思的是。这个还满足向量的自反性。
X->Y = 1.
Y->X = -1.为了让其在0~2范围内。
Y->X = (-1+3)%3 = 2.满足我们上述的关系。(这里的操作其实是数论上的同余定理的小使用)

程序怎么写就不用说了吧。因为这样设值满足了类向量加法了已经。就是如果取反。要减去那个值。

其实如果能理解到这。上面留下来的问题也就已经解决掉了。我们并不需要整套的满足向量运算。甚至我们其实不用满足所谓的向量加法。
我们只是为了我们的关系能够比较好地发生转移。

比如食物链那个题目。如果改为A->B,B->C 那么可以获得A->C。你要怎么做处理?
其实很简单。只要设X->Y为1.
A->C = A->B|B->C
虽然还是满足形式上的类向量加法。
但是你必须知道的是我们只是为了我们的关系更好地转移而这样做的。而且可以适应多元的运算。
否则的话你用逻辑穷举所有可能性。也是可以的。只不过这里把逻辑运算做成了类似的加法运算。
比如随意地、
我要给A->C赋关系的时候。我可以特判。A->B 的关系如何如何。B->C的关系如何如何。那么A->C的关系就是如何如何。
当然这样会很麻烦。但是也不是不可以。所以本质上就是为了更好的关系转移而已。然后加上维护并查集信息的核心思想。

其实我很想能够有一个方法比较好地推导出我们该设的值。而不是无意义地尝试(尽管你会发现我们要设的值总是0开始然后几个自然数)。

时间: 2024-08-26 13:49:50

并查集小结的相关文章

可持久化并查集小结

https://www.zybuluo.com/ysner/note/1253722 定义 允许恢复历史状态的并查集. 建立 建\(Q\)棵主席树,每个主席树上维护当前状态并查集各个节点的父亲. (实际上就是并查集和主席树强行捆绑在一起) 操作 每次操作前自动继承上次操作后的状态. 合并\(a,b\)所在集合 把两棵主席树按秩合并(深度大的合并到深度小的). 如果两棵主席树合并时深度相等,给合并后的主席树深度\(+1\)(要不然哪来的秩) 回到第\(k\)次操作之后的状态 把当前主席树的根赋值为

数据结构专题小结:并查集

并查集 并查集的作用是快速判断两个数是否属于同一类的数据结构,不过除此之外,它还可以实现合并u和v所在的组.下面给出并查集的一系列操作的实现. #define N 100 int par[N]; int rnk[N]; void init(int n)//初始化n个元素 { for (int i = 0; i < n; i++) { par[i] = i; rnk[i] = 0; } } int find(int x)//查询树的根 { return par[x] == x ? x : par[

小结:并查集

复杂度: O(n*α(n)) 其中α(x),对于x=宇宙中原子数之和,α(x)不大于4 .(对于nocow里的复杂度我也是醉了) 概要: 并查集就是一个数组和一行话. 应用: 图的连通.集合操作.生成树的合并等 技巧及注意: 并查集是个好东西. 维护区间+前缀和:对于一些连续的区间,我们要判断这些区间是否合法,带修改.这种题我们可以考虑并查集来维护区间,用前缀和维护信息,而在并查集按秩合并的时候,一定要注意合并前和合并后如何维护信息,比如这题[BZOJ]1202: [HNOI2005]狡猾的商人

并查集知识学习

(转) 并查集的作用:并和查,即合并和查找,将一些集合合并,快速查找或判断某两个集合的关系,或某元素与集合的关系,或某两个元素的关系. 并查集的结构:并查集主要操作对象是森林,树的结构赋予它独特的能力,对整个集合操作转换为对根节点(或称该集合的代表元素)的操作,一个集合里的元素关系不一定确定,但相对于根节点的关系很明了,这也是为了查找方便. 并查集优化方法:按秩合并和路径压缩的配合使用,使得查找过程优化到极致.按秩合并,每次将深度小的树合并到深度大的树里面去,使得整棵树尽量矮:路径压缩,将当前节

[bzoj1455]罗马游戏_左偏树_并查集

罗马游戏 bzoj-1455 题目大意:给你n个人,2种操作,m次操作:1.将i号士兵所在的集合的最小值删除 2.合并i和j两个士兵所在的团体 注释:$1\le n\le 10^6$,$1\le m \le 10^5$. 想法:又是GXZlegend讲课,可并堆中的左偏树.了解一下: 一个具有堆性质的二叉树满足任意一个节点x中,dis[lson[x]]>=dis[rson[x]],其中,dis表示当前节点一直走右儿子的最长步数.合并是递归合并,我们通过递归处理一两个节点为根节点的左偏树的合并,显

[bzoj3669][Noi2014]魔法森林_LCT_并查集

魔法森林 bzoj-3669 Noi-2014 题目大意:说不明白题意系列++……题目链接 注释:略. 想法:如果只有1个参量的话spfa.dij什么的都上来了. 两个参量的话我们考虑,想将所有的边按照a排序. 如果两个点:它们之间有两条路径,有一条比另一条劣. 那么我们完全可以将另一条弄掉. 排序之后维护生成树. LCT的点维护的是实子树中第二参量的最大值. 如果当前边连接的两点之前不连通,直接连上. 如果联通,我们判断新边的第二参量和两点之间splay的最大参量之间的关系. 如果新边的第二参

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

什么是并查集? 合并!查询!集合! 专业点说? 动态维护若干不重叠的和,支持合并查询的数据结构!(lyd老师说的) 数据结构特点:代表元.即为每个集合选择一个固定的元素,作为整个集合的代表,利用树形结构存储,每个节点都是一个元素,树根是集合的代表元素.(还是lyd老师说的) 两大基本操作 一.合并(merge()) 即把两个集合合并到一个的操作.通俗的说,即令其中一个树根为另一个树根的子节点. void merge(int x,int y) { fa[getf(x)]=getf(y); } 二.

[bzoj2443][Usaco2011 Open]奇数度数_树形dp_生成树_并查集

奇数度数 bzoj-2443 Usaco-2011 Open 题目大意:给定一个n个点m条便有向图,问是否有一种选出一些边的方式使得所有点的度数都是奇数. 注释:$1\le n \le 5\cdot 10^4$,$1\le m\le 10^5$. 想法: 结论题:对于一个联通块来讲,如果求出它的生成树.只考虑生成树上的边的选取情况是否可能即是这个联通块的答案. 证明:如果存在一种,选取生成树以外的边满足题意,我们可以将这条边覆盖的树边全部取反,将该边舍去,仍然满足题意. 故此,用并查集求出生成树

畅通工程(并查集模版题)

题意: 多组输入N,M,当N为0退出人输入,N是道路数目,M是村庄总数,随后N行,每行输入三个数两个村庄的编号,以及连接这两个村庄的费用.对每一组数据输出畅通工程的最低费用,如果不能畅通就输出“?”(不包括双引号) 这道题有两道链接: 一道是fjut的链接,另外一道是hdu的 http://www.fjutacm.com/Problem.jsp?pid=1214 http://acm.hdu.edu.cn/showproblem.php?pid=1863 思路:其实这道题就是一道排序+并查集题,