并查集
并查集
并查集:(union-find sets)是一种简单的用途广泛的集合. 并查集是若干个不相交集合,能够实现较快的合并和判断元素所在集合的操作。
并查集的两种基本操作:
(1)合并;
(2)查询;
初始化:
1
2
3初始化: 每一个结点的上级都是自己,也就是说独立成一个集合 合并集合操作:
for(i=1;i<=n;i++)
f[i]=i;合并:
1
f[find(p2)]=find(p3);
查询:
1
2
3
4int find(int k){
if(f[k]==k)return k;
return f[k]=find(f[k]);
}总代码
1 |
|
扩展域并查集
拓展域并查集解决了一种多个有相互关系的并查集,放在一起考虑的问题。一般的并查集应用一般就是判断在不在一个集合,拓展域并查集讲的是多个集合,之间有相互关系一般为相互排斥关系,判断是否在一个集合等。
背景:
并查集能维护连通性、传递性,通俗地说,
亲戚的亲戚是亲戚
。然而当我们需要维护一些对立关系,比如
敌人的敌人是朋友
时,正常的并查集就很难满足我们的需求。这时,
扩展域并查集
就诞生了。常见的做法是将原并查集扩大一倍规模,并划分为两个种类。
在同个种类的并查集中合并,和原始的并查集没什么区别,仍然表达
他们是朋友
这个含义。考虑在不同种类的并查集中合并的意义,其实就表达
他们是敌人
这个含义了。按照并查集美妙的
传递性
,我们就能具体知道某两个元素到底是敌人
还是朋友
了。具体实现,详见
P1525 关押罪犯
。
[NOIP2010 提高组] 关押罪犯
题目背景
NOIP2010 提高组 T3
题目描述
S 城现有两座监狱,一共关押着 \(N\) 名罪犯,编号分别为 \(1\sim N\)。他们之间的关系自然也极不和谐。很多罪犯之间甚至积怨已久,如果客观条件具备则随时可能爆发冲突。我们用“怨气值”(一个正整数值)来表示某两名罪犯之间的仇恨程度,怨气值越大,则这两名罪犯之间的积怨越多。如果两名怨气值为 \(c\) 的罪犯被关押在同一监狱,他们俩之间会发生摩擦,并造成影响力为 \(c\) 的冲突事件。
每年年末,警察局会将本年内监狱中的所有冲突事件按影响力从大到小排成一个列表,然后上报到 S 城 Z 市长那里。公务繁忙的 Z 市长只会去看列表中的第一个事件的影响力,如果影响很坏,他就会考虑撤换警察局长。
在详细考察了 \(N\) 名罪犯间的矛盾关系后,警察局长觉得压力巨大。他准备将罪犯们在两座监狱内重新分配,以求产生的冲突事件影响力都较小,从而保住自己的乌纱帽。假设只要处于同一监狱内的某两个罪犯间有仇恨,那么他们一定会在每年的某个时候发生摩擦。
那么,应如何分配罪犯,才能使 Z 市长看到的那个冲突事件的影响力最小?这个最小值是多少?
输入格式
每行中两个数之间用一个空格隔开。第一行为两个正整数 \(N,M\),分别表示罪犯的数目以及存在仇恨的罪犯对数。接下来的 \(M\) 行每行为三个正整数 \(a_j,b_j,c_j\),表示 \(a_j\) 号和 \(b_j\) 号罪犯之间存在仇恨,其怨气值为 \(c_j\)。数据保证 \(1<a_j\leq b_j\leq N, 0 < c_j\leq 10^9\),且每对罪犯组合只出现一次。
输出格式
共一行,为 Z
市长看到的那个冲突事件的影响力。如果本年内监狱中未发生任何冲突事件,请输出
0
。
样例 #1
样例输入 #1
1 | 4 6 |
样例输出 #1
1 | 3512 |
提示
输入输出样例说明
罪犯之间的怨气值如下面左图所示,右图所示为罪犯的分配方法,市长看到的冲突事件影响力是 \(3512\)(由 \(2\) 号和 \(3\) 号罪犯引发)。其他任何分法都不会比这个分法更优。
数据范围
对于 \(30\%\) 的数据有 \(N\leq 15\)。
对于 \(70\%\) 的数据有 \(N\leq 2000,M\leq 50000\)。
对于 \(100\%\) 的数据有 \(N\leq 20000,M\leq 100000\)。
1 |
|
优点在于:结构简单,并查集的操作也不需要做改变,非常易于理解。 缺点显然就是:需要额外存储空间。
[NOI2001] 食物链
题目描述
动物王国中有三类动物 \(A,B,C\),这三类动物的食物链构成了有趣的环形。\(A\) 吃 \(B\),\(B\) 吃 \(C\),\(C\) 吃 \(A\)。
现有 \(N\) 个动物,以 \(1 \sim N\) 编号。每个动物都是 \(A,B,C\) 中的一种,但是我们并不知道它到底是哪一种。
有人用两种说法对这 \(N\) 个动物所构成的食物链关系进行描述:
- 第一种说法是
1 X Y
,表示 \(X\) 和 \(Y\) 是同类。 - 第二种说法是
2 X Y
,表示 \(X\) 吃 \(Y\)。
此人对 \(N\) 个动物,用上述两种说法,一句接一句地说出 \(K\) 句话,这 \(K\) 句话有的是真的,有的是假的。当一句话满足下列三条之一时,这句话就是假话,否则就是真话。
- 当前的话与前面的某些真的话冲突,就是假话;
- 当前的话中 \(X\) 或 \(Y\) 比 \(N\) 大,就是假话;
- 当前的话表示 \(X\) 吃 \(X\),就是假话。
你的任务是根据给定的 \(N\) 和 \(K\) 句话,输出假话的总数。
输入格式
第一行两个整数,\(N,K\),表示有 \(N\) 个动物,\(K\) 句话。
第二行开始每行一句话(按照题目要求,见样例)
输出格式
一行,一个整数,表示假话的总数。
样例 #1
样例输入 #1
1 | 100 7 |
样例输出 #1
1 | 3 |
提示
对于全部数据,\(1\le N\le 5 \times 10^4\),\(1\le K \le 10^5\)。
1 |
|
带权并查集
基本概念
带权并查集即是结点存有权值信息的并查集;当两个元素之间的关系可以量化,并且关系可以合并时,可以使用带权并查集来维护元素之间的关系;带权并查集每个元素的权通常描述其与并查集中祖先的关系,这种关系如何合并,路径压缩时就如何压缩;带权并查集可以推算集合内点的关系,而一般并查集只能判断属于某个集合。
如上文的食物链问题
这个题目需要维护推算集合内部的关系,所以可以利用带权并查集解决。
创建利用pre数组和rela数组判断集合关系,pre判断集合之间的关系,rela判断集合内部元素的关系,这题我们可以建立三种关系同类,捕食,和被捕食三种关系,我们在rela数组中分别用0,1,2表示:
- 0表示和根节点是同类关系
- 1表示和跟节点是捕食关系(吃根节点)
- 2表示和根节点是被捕食关系(被根节点吃)
首先是合并考虑压缩路径时的关系维护,我们压缩路径时已知a和b的关系,以及a和a根节点的关系,需要推导出b和a根节点的关系,如图是我们橙色线是我们要推导出的关系,黑色线是以知关系。
结点A与根关系 | 结点B与A关系 | B与根关系 |
---|---|---|
0 | 0 | 0 |
0 | 1 | 1 |
0 | 2 | 2 |
1 | 0 | 1 |
1 | 1 | 2 |
1 | 2 | 0 |
2 | 0 | 2 |
2 | 1 | 0 |
2 | 2 | 1 |
1 | int Find(int x){ // 查找当前结点的根节点 |
然后我们考虑关系的查找,我们以及知道A和B在同一集合,即代表他们根节点相同,我们要确定两者之间的关系,我们还是线画出关系图,橙色线是我们要推导出的关系,黑色线是以知关系。
结点A与根关系 | 结点B与根关系 | A与B关系 |
---|---|---|
0 | 0 | 0 |
0 | 1 | 2 |
0 | 2 | 1 |
1 | 0 | 1 |
1 | 1 | 0 |
1 | 2 | 2 |
2 | 0 | 2 |
2 | 1 | 1 |
2 | 2 | 0 |
1 | if(Find(x) == Find(y)){ // 如果两个根节点相同 |
最后我们考虑合并两个节点时关系的维护,我们已经知a和其根节点的关系,以及b和其根节点的关系,当我们把b集合合并到a集合时,我们需要考虑b根节点和a根节点存在的关系,关系图如下,橙色线是我们要推导出的关系,黑色线是以知关系。
结点A与根关系 | 结点B与根关系 | 结点B与A的关系 | B根节点和A根节点的关系 |
---|---|---|---|
0 | 0 | 0 | 0 |
0 | 0 | 1 | 1 |
0 | 0 | 2 | 2 |
0 | 1 | 0 | 2 |
0 | 1 | 1 | 0 |
0 | 1 | 2 | 1 |
0 | 2 | 0 | 1 |
0 | 2 | 1 | 2 |
0 | 2 | 2 | 0 |
1 | void Merge(int x, int y, int r){ // 合并两个节点关系 |
1 | //总代码 |