扩展域并查集(种类并查集)

总算理解种类并查集了,浅说一下吧。

有大佬已经讲得很清楚了,戳这 -> 作者:OIer某罗


先看[NOIP2010 提高组] 关押罪犯这题。

[思路]:

我就再讲讲理解:
种类并查集像分类讨论,比如这题,我们考虑的是:原本所有罪犯都在 $A$ 监狱,一个一个扔到 $B$ 监狱。

但不能决定谁到 \(B\) 监狱去,所以用种类并查集分类讨论,找到最坏的无法避免的冲突。

[NOIP2010 提高组] 关押罪犯 Code:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e4 + 5, M = 2e5 + 5;
int n, m, fa[N << 1];
//1~n 表示A监狱 n+1~2*n 表示B监狱
struct ss {
	int u, v, w;
}a[M];
inline int read() {
	int s = 0, f = 1; char c = getchar();
	while (c < '0' || c>'9') { if (c == '-')f = -1; c = getchar(); }
	while (c >= '0' && c <= '9') { s = (s << 1) + (s << 3) + (c ^ 48); c = getchar(); }
	return s * f;
}
bool cmp(ss a, ss b) {
	return a.w > b.w;
}
inline int find(int x) {
	if (fa[x] == x)return x;
	return fa[x] = find(fa[x]);
}
void unite(int x, int y) {
	x = find(x); y = find(y);
	if (x == y)return;
	fa[x] = y;
}
/*
wrong-solution:
	原本所有人都在A监狱,一个一个扔到B监狱,
	但两个人不能决定谁去B,出问题了。

real-solution:
	用种类并查集,一个人在数组中对应2位置,在A监狱位置和在B的位置
	记录关系的话开两倍大小,一种 1号去A,2号去B ;一种 2号去A,1号去B
	问题就解决了。
*/
int main() {
	cin >> n >> m;
	for (int i = 0; i <= n << 1; i++)fa[i] = i;
	for (int i = 1; i <= m; i++)a[i].u = read(), a[i].v = read(), a[i].w = read();
	sort(a + 1, a + m + 1, cmp);//贪心
	for (int i = 1; i <= m + 1; i++) {//到m+1就可以不用特判0的情况,因为a[m+1].u a[m+1].v a[m+1].w 都是0
		int u = a[i].u, v = a[i].v;
		if (find(u) == find(v) || find(u + n) == find(v + n)) { cout << a[i].w; break; }
		//已经在同一个监狱就不分类讨论了,都已经打起来了
		unite(u, v + n); unite(u + n, v);
		//分类讨论,一个到A监狱,一个到B监狱
	}
	return 0;
}

[NOI2001] 食物链

[思路]:

同样,每次 \(2\) 操作我们不知道 \(x\)\(y\) 到底属于哪一层,那每种情况都考虑一遍。

对于操作一,连同类时不满足的情况有:
  1. \(x\)\(y\)
  2. \(y\)\(x\)

\(\,\)

对于操作二,连天敌是不满足的情况有:
  1. \(x\)\(y\) 是同类
  2. \(y\)\(x\)

由于连边每层是相同的,所以只需要判断第一层的就行了。

[NOI2001] 食物链 Code:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 5e4 + 5;
int n, k, fa[3 * N];
/*
A类: 1 ~ n
B类: n+1 ~ 2*n
C类: 2*n+1 ~ 3*n

A吃B  B吃C  C吃A
*/
int ans;
inline int read() {
	int s = 0, f = 1; char c = getchar();
	while (c < '0' || c>'9') { if (c == '-')f = -1; c = getchar(); }
	while (c >= '0' && c <= '9') { s = (s << 1) + (s << 3) + (c ^ 48); c = getchar(); }
	return s * f;
}
int find(int x) {
	if (x == fa[x])return x;
	return fa[x] = find(fa[x]);
}
void unite(int x, int y) {//x 吃 y 
	if (x == y)return;
	fa[x] = y;
}
int main() {
	n = read(), k = read();
	for (int i = 1; i <= 3 * n; i++)fa[i] = i;
	while (k--) {
		int opt = read(), x = read(), y = read();
		if (x > n || y > n || (opt == 2 && x == y)) { ans++; continue; }
		int Ax = find(x), Bx = find(x + n), Cx = find(x + 2 * n);
		int Ay = find(y), By = find(y + n), Cy = find(y + 2 * n);
		if (opt == 1) {//同类 
			if (Ax == By || Bx == Ay) { ans++; continue; }  //不正确的情况
			//三层中连得边都相同,所以只用判断一层的了
			//有两种情况能反驳x与y是同类,一种x吃y ;一种y吃x 
			unite(Ax, Ay); unite(Bx, By); unite(Cx, Cy);//每层都连 
		}
		if (opt == 2) {//天敌 
			if (Ax == Ay || Ay == Bx) { ans++; continue; }  //不正确的情况
			//同上,有两种情况能反驳x吃y,一种x与y是同类,一种y吃x 
			unite(Ax, By); unite(Bx, Cy); unite(Cx, Ay);
			//每层x都吃下一层的y 
		}
	}
	cout << ans;
	return 0;
}
posted @ 2023-08-08 01:53  今添  阅读(38)  评论(0)    收藏  举报