回顾:并查集分为简单并查集、扩展域并查集、带权并查集三种,第一种一般用于解决单元关系问题,后两种可以解决多关系问题。
下面是上篇博客的题单。
简单并查集:
扩展域并查集:
带权并查集:
一、3367 【模板】并查集 - 洛谷
题⽬来源: 洛⾕
题⽬链接:
P3367 【模板】并查集
难度系数: ★★
【解法】:模板题~
🖥️code:
#include <iostream>
using namespace std;
const int N = 1e5 + 10;
int fa[N];
int n;
int find(int x)
{
if(x == fa[x]) return x;
// 路径压缩
return fa[x] = find(fa[x]);
//return x == fa[x] ? x : fa[x] = find(fa[x]);
}
void un(int x, int y) // 不要起union
{
int fx = find(x);
int fy = find(y);
// 修改指向
fa[x] = fy;
}
bool issame(int x, int y)
{
int fx = find(x);
int fy = find(y);
return fx == fy;
}
int main()
{
cin >> n;
for(int i = 1; i <= n; i++) fa[i] = i;
return 0;
}
二、P1551 亲戚 - 洛谷
题⽬来源: 洛⾕
题⽬链接:
P1551 亲戚
难度系数: ★★

【解法】:并查集的简单应用~
🖥️code:
#include <iostream>
using namespace std;
const int N = 5010;
int n, m, p;
int fa[N];
int find(int x)
{
return x == fa[x] ? x : fa[x] = find(fa[x]);
}
void un(int x, int y)
{
int fx = find(x);
int fy = find(y);
// 修改指向
fa[fx] = fy;
}
bool issame(int x, int y)
{
return find(x) == find(y);
}
int main()
{
cin >> n >> m >> p;
// init
for(int i = 1; i <= n; i++) fa[i] = i;
//un
for(int i = 1; i <= m; i++)
{
int x, y; cin >> x >> y;
un(x, y);
}
// query
for(int i = 1; i <= p; i++)
{
int x, y; cin >> x >> y;
if(issame(x, y)) cout << "Yes" << endl;
else cout << "No" << endl;
}
return 0;
}
三、P1596 [USACO10OCT] Lake Counting S - 洛谷
题⽬来源: 洛⾕
题⽬链接:
P1596 [USACO10OCT] Lake Counting S
难度系数: ★★

【解法】:本题也可以用搜索的floodfill算法解决。求水坑的数量就是找连通的W区域共有多少个,
我们可以用并查集把同一区域的W合并在一起。
问题1:遍历时如何找到与之相同的W? 遍历到W时把右、下、右下、左下方向的W合并在一起。
问题2:如何将二维矩阵信息存储到一维并查集中呢? 二维坐标转一维即可~(注意下标从零开始)。
问题3:如何判断最终有几个水坑? 遍历一下fa数组找有几多个大哥~(自己指向自己的结点)。
🖥️code:
#include <iostream>
using namespace std;
const int N = 110;
char a[N][N];
int n, m;
int fa[N * N];
// 方向向量
int dx[] = {1, 1, 0, -1};
int dy[] = {0, 1, 1, 1};
int find(int x)
{
return x == fa[x] ? x : fa[x] = find(fa[x]);
}
void un(int x, int y)
{
fa[find(x)] = find(y);
}
void bfs()
{
for(int i = 0; i < n; i++)
{
for(int j = 0; j < m; j++)
{
for(int k = 0; k < 3; k++)
{
int x = i + dx[k];
int y = j + dy[k];
// 合并时转化成一位坐标
if(a[x][y] == 'W' && y >= 0) un(i * m + j, x * m + y);
}
}
}
}
int main()
{
cin >> n >> m;
// init
for(int i = 0; i < n * m; i++) fa[i] = i;
for(int i = 0; i < n; i++)
{
for(int j = 0; j < m; j++)
{
cin >> a[i][j];
}
}
// 合并水坑
bfs();
// 统计数量
int ret = 0;
for(int i = 0; i < n * m; i++)
{
if(fa[i] == i && a[i / m][i % m] == 'W') ret++;
}
cout << ret << endl;
return 0;
}
四、P1955 [NOI2015] 程序自动分析 - 洛谷
题⽬来源: 洛⾕
题⽬链接:
P1955 [NOI2015] 程序⾃动分析
难度系数: ★★★



【解法】:本题的思路很简单,e = 1时,将其合并;e = 0时,判断是否在同一个集合。
但是本题的数据并不是很友好,我们开不了1e9规模的数组,然而n的规模只有1e5,也就是说:本题的数据很分散,所以我们需要
离散化算法辅助。
🖥️code:
#include <iostream>
#include <unordered_map>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e5 + 10;
int fa[N * 2];
int T, n;
struct node {
int i, j, e;
}a[N];
// 离散化
int pos, disc[N * 2];
unordered_map<int, int> mp;
int find(int x)
{
return x == fa[x] ? x : fa[x] = find(fa[x]);
}
void un(int x, int y)
{
int fx = find(x), fy = find(y);
fa[fx] = fy;
}
bool issame(int x, int y)
{
return find(x) == find(y);
}
bool check()
{
// 一定要先合并再判断
for (int i = 1; i <= n; i++)
{
int x = a[i].i, y = a[i].j, op = a[i].e;
if (op == 1) un(mp[x], mp[y]);
}
for (int i = 1; i <= n; i++)
{
int x = a[i].i, y = a[i].j, op = a[i].e;
if (op == 0)
{
if (issame(mp[x], mp[y])) return false;
}
}
return true;
}
int main()
{
cin >> T;
while (T--)
{
cin >> n;
// 清空数据
pos = 0;
mp.clear();
for (int i = 1; i <= n; i++)
{
cin >> a[i].i >> a[i].j >> a[i].e;
disc[++pos] = a[i].i;
disc[++pos] = a[i].j;
}
// 离散化
sort(disc + 1, disc + 1 + pos);
int cnt = 0;
for (int i = 1; i <= pos; i++)
{
int x = disc[i];
if (mp.count(x)) continue;
cnt++;
mp[x] = cnt;
}
// init 此时cnt表示不相同元素个数
for (int i = 1; i <= cnt; i++) fa[i] = i;
if (check()) cout << "YES" << endl;
else cout << "NO" << endl;
}
return 0;
}
五、P1892 [BalticOI 2003] 团伙 - 洛谷
题⽬来源: 洛⾕
题⽬链接:
P1892 [BOI2003] 团伙
难度系数: ★★★
【解法】:本题的关系集合有两个,一个是朋友域,一个是敌人域。采用扩展域并查集解题时要注意合并时,把两个域同时合并。
• a 和 b 如果是朋友,那就直接合并在⼀起;
• a 和 b 如果是敌⼈,那就把 a 和 b + n 以及 a + n 和 b 合并在⼀起。
tip:为了方便我们后续统计团伙的数量,在合并时新的根结点统一为朋友域的结点,就当敌人域是假象的。
🖥️code:
#include <iostream>
using namespace std;
const int N = 1010;
int n, m;
// 扩展两个域
int fa[N * 2];
int find(int x)
{
return x == fa[x] ? x : fa[x] = find(fa[x]);
}
void un(int x, int y)
{
fa[find(x)] = find(y);
}
int main()
{
cin >> n >> m;
for(int i = 1; i <= n * 2; i++) fa[i] = i;
while(m--)
{
char op;
int x, y;
cin >> op >> x >> y;
if(op == 'E')
{
un(x + n, y);
un(y + n, x);
}
else un(x, y);
}
int ret = 0;
for(int i = 1; i <= n; i++)
{
if(fa[i] == i) ret++;
}
cout << ret << endl;
return 0;
}
六、P2024 [NOI2001] 食物链 - 洛谷
题⽬来源: 洛⾕
题⽬链接:
P2024 [NOI2001] ⻝物链
难度系数: ★★★
【解法】:本题同样也是多关系问题,我们采用扩展域并查集解决。
如果 x 和 y 是同类:
• x 和 y 是同类
• x + n 与 y + n 是同类
• x + n + n 与 y + n + n 是同类
如果 x 捕⻝ y:
• x + n 与 y 同类
• x 与 y + n + n 同类
• x + n + n 与 y + n 同类
如何验证当前的话与前面冲突?
1. x与y是同类,则x 与y + n 或 y + 2 * n是同类就是假话。
2. x 捕食y,x与y或者y + 2 * n是同类就是假话。
🖥️code:
#include <iostream>
using namespace std;
const int N = 5e4 + 10;
int fa[N * 3];
int n, k;
int find(int x)
{
return x == fa[x] ? x : fa[x] = find(fa[x]);
}
void un(int x, int y)
{
fa[find(x)] = find(y);
}
bool issame(int x, int y)
{
return find(x) == find(y);
}
int main()
{
cin >> n >> k;
for(int i = 1; i <= n * 3; i++) fa[i] = i;
int ret = 0;
while(k--)
{
int op, x, y; cin >> op >> x >> y;
if(x > n || y > n)
{
ret++;
continue;
}
// 同类合并
if(op == 1)
{
if(issame(x, y + n) || issame(x, y + 2 * n))
{
ret++;
}
else
{
un(x, y);
un(x + n, y + n);
un(x + 2 * n, y + 2 * n);
}
}
// x捕食y
else
{
if(issame(x, y) || issame(x, y + 2 * n))
{
ret++;
}
else
{
un(x, y + n);
un(x + n, y + 2 * n);
un(x + 2 * n, y);
}
}
}
cout << ret << endl;
return 0;
}
七、P2024 [NOI2001] 食物链 - 洛谷 带权并查集版
题⽬来源: 洛⾕
题⽬链接:
P2024 [NOI2001] ⻝物链
难度系数: ★★★

【解法】:

🖥️code:
#include<iostream>
using namespace std;
const int N = 5e4 + 10;
int fa[N], d[N];
int n, k;
int find(int x)
{
if(x == fa[x]) return x;
int t = find(fa[x]);
d[x] += d[fa[x]];
return fa[x] = t;
}
void un(int x, int y, int w)
{
int fx = find(x), fy = find(y);
if(fx != fy)
{
fa[fx] = fy;
d[fx] = d[y] + w - d[x];
}
}
int main()
{
cin >> n >> k;
for(int i = 1; i <= n; i++)
{
fa[i] = i;
d[i] = 0;
}
int ret = 0;
while(k--)
{
int op, x, y; cin >> op >> x >> y;
if(x > n || y > n) ret++;
else
{
if(op == 1)
{
if(find(x) == find(y) && ((d[y] - d[x]) % 3 + 3) % 3!= 0) ret++;
else
{
un(x, y, 0);
}
}
else
{
if(find(x) == find(y) && ((d[y] - d[x]) % 3 + 3) % 3 != 1) ret++;
else
{
un(x, y, 2);
}
}
}
}
cout << ret << endl;
return 0;
}
八、P1196 [NOI2002] 银河英雄传说 - 洛谷
题⽬来源: 洛⾕
题⽬链接:
P1196 [NOI2002] 银河英雄传说
难度系数: ★★★
🖥️code:
#include <iostream>
using namespace std;
const int N = 3e4 + 10;
int n = 3e4;
int fa[N], d[N], cnt[N];
int find(int x)
{
if(fa[x] == x) return x;
int t = find(fa[x]);
d[x] += d[fa[x]];
return fa[x] = t;
}
void un(int x, int y)
{
int fx = find(x), fy = find(y);
if(fx != fy)
{
fa[fx] = fy;
d[fx] += cnt[fy];
cnt[fy] += cnt[fx];
}
}
int query(int x, int y)
{
int fx = find(x), fy = find(y);
if(fx == fy) return abs(d[y] - d[x]) - 1;
else return -1;
}
int main()
{
for(int i = 1; i <= n; i++)
{
fa[i] = i;
cnt[i] = 1;
}
int T; cin >> T;
while(T--)
{
char op; int x, y; cin >> op >> x >> y;
if(op == 'M')
{
un(x, y);
}
else
{
cout << query(x, y) << endl;
}
}
return 0;
}