扩展域并查集(种类并查集)
总算理解种类并查集了,浅说一下吧。
有大佬已经讲得很清楚了,戳这 -> 作者: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\) 到底属于哪一层,那每种情况都考虑一遍。
对于操作一,连同类时不满足的情况有:
- \(x\) 吃 \(y\)
- \(y\) 吃 \(x\)
\(\,\)
对于操作二,连天敌是不满足的情况有:
- \(x\) 与 \(y\) 是同类
- \(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;
}

浙公网安备 33010602011771号