种类并查集学习笔记

前置芝士🧀

在学习种类并查集之前肯定要学并查集啊~

为什么有种类并查集

正常的并查集一般是维护一个友好关系的传递性,例如朋友的朋友是朋友,可更多题目中,个体间的关系就没那么简单了,需要维护类似敌人的敌人是朋友。这时,便有人想出将他们分为几个种类类,并用并查集维护种类间的关系

典型案例

敌人的敌人是朋友 An Enemy's Enenmy is a Friend

P1525 [NOIP2010 提高组] 关押罪犯就是一个种类并查集很典型的问题。

[NOIP2010 提高组] 关押罪犯

题目背景

NOIP2010 提高组 T3

题目描述

S 城现有两座监狱,一共关押着 \(N\) 名罪犯,编号分别为 \(1\sim N\)。他们之间的关系自然也极不和谐。很多罪犯之间甚至积怨已久,如果客观条件具备则随时可能爆发冲突。我们用“怨气值”(一个正整数值)来表示某两名罪犯之间的仇恨程度,怨气值越大,则这两名罪犯之间的积怨越多。如果两名怨气值为 \(c\) 的罪犯被关押在同一监狱,他们俩之间会发生摩擦,并造成影响力为 \(c\) 的冲突事件。

每年年末,警察局会将本年内监狱中的所有冲突事件按影响力从大到小排成一个列表,然后上报到 S 城 Z 市长那里。公务繁忙的 Z 市长只会去看列表中的第一个事件的影响力,如果影响很坏,他就会考虑撤换警察局长。

在详细考察了 \(N\) 名罪犯间的矛盾关系后,警察局长觉得压力巨大。他准备将罪犯们在两座监狱内重新分配,以求产生的冲突事件影响力都较小,从而保住自己的乌纱帽。假设只要处于同一监狱内的某两个罪犯间有仇恨,那么他们一定会在每年的某个时候发生摩擦。

那么,应如何分配罪犯,才能使 Z 市长看到的那个冲突事件的影响力最小?这个最小值是多少?

解法

通过一点点的贪心,我们可以先将所有的关系依据怨气值自高至低排序,再通过某种方式判断哪对矛盾不能被化解。
我们采用的方法就是种类并查集。我们给我们的并查集加一倍的空间。
image
比如要建立1与2的敌对关系,就可以如下图箭头对两个点union:
image
而当建立2与3的敌对关系时候:
image
此时,通过观察可以发现,我们成功的维护了敌人的敌人是朋友这一关系,1与其敌人2的敌人3在同一个并查集中。
而在这道题中我们只需要通过它来找出第一个冲突就OK啦

剪刀石头布 Rock, Paper, Scissors

在玩剪刀石头布的时候,我们都知道他们间存在着这样一种关系,石头砸剪刀,布包石头,剪刀剪布。P2024 [NOI2001] 食物链一题就展示了一种类似这样的关系,是一种三角的捕食关系。

[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\) 句话,输出假话的总数。

解法

这一题的要求比前面那题更简单,只需判断,果断种类并查集:
开三倍空间把这玩意存下去
image
当需要建立是同类这一关系时,直接将同组内的那两个点连接。比如连接1、3时:
image
而当表示1吃4时,连接不同组的1和4:
image

代码实现

点击查看代码
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 5e4+8;
class DSU{
private:
	int fa[N<<2];
	int dep[N<<2];
public:
	inline void init(int n){
		n *= 3;	//三倍空间
		for(int i = 1; i <= n; i++)
			fa[i] = i, dep[i] = 0;
	}
	inline int find(int x){
		if(x == fa[x])
			return x;
		return fa[x] = find(fa[x]);
	}
	inline bool check(int x, int y){
		return find(x) == find(y);
	}
	inline void join(int x, int y){	//秩排序 + 压缩路径 = 飞快
		x = find(x);
		y = find(y);
		if(x == y)
			return;
		if(dep[x] > dep[y])
			swap(x, y);
		if(dep[x] == dep[y])
			dep[y]++;
		fa[x] = y;
	}
};
DSU UN;

int main(){
	int n, k;
	cin >> n >> k;
	int cnt = 0;
	UN.init(n);
	for(int i = 1; i <= k; i++){
		int x, y, op;
		cin >> op >> x >> y;
		if(x > n || y > n){
			cnt++;
			continue;
		}
		if(op == 1){
			if(UN.check(x, y+n) || UN.check(x, y+2*n))	//存在捕食关系
				cnt++;
			else{
				UN.join(x, y);
				UN.join(x+n, y+n);
				UN.join(x+2*n, y+2*n);
			}
		}
		else{
			if(UN.check(x, y) || UN.check(x, y+2*n))	//与给出关系相悖
				cnt++;
			else{
				UN.join(x, y+n);
				UN.join(x+n, y+2*n);
				UN.join(x+2*n, y);
			}
		}
	}
	cout << cnt << endl;
	return 0;
}

结语

对运用并查集的思路一下子打开了~

posted @ 2024-07-10 15:52  HurryCine  阅读(80)  评论(0)    收藏  举报