并查集(英文:Disjoint-set data structure,不交集数据结构)用于处理一些不交集(Disjoint sets,一系列没有重复元素的集合)的合并及查询问题
时间复杂度与空间复杂度
- 时间复杂度: O(ɒ(n))
- 空间复杂度: O(n)
优缺点
- 优点:网络路由、符号计算、寄存器分配方面有优势 😉
核心代码
查询
查询某个元素属于哪个集合,通常是返回集合内的一个“代表元素”。这个操作是为了判断两个元素是否在同一个集合之中。
int Find(int x) { return x == f[x] ? x : f[x] = Find(f[x]); }
/** 注意 返回 f[x] = Find(f[x]) ! */
合并
将两个集合合并为一个。
void Union(int x, int y) { f[Find(x)] = Find(y); }
// int x_root = Find(x), y_root = Find(y);
// if (x_root != y_root) f[x_root] = y_root;
添加
添加一个新集合,其中有一个新元素。添加操作不如查询和合并操作重要,常常被忽略。
void Add(int x) { F[x] = x; }
练习题目
1. A Bug’s Life
题目描述
两两虫子相互多次交配,找出同性恋虫子。
AC代码
/**
* https://vjudge.csgrandeur.cn/problem/HDU-1829
* \category: 并查集
*
*
**/
#include <algorithm>
#include <cmath>
#include <cstring>
#include <iostream>
#include <numeric>
#include <set>
using namespace std;
int n;
int t = 0;
const int kN = 100100;
int f[2 * kN];
int Find(int x) { return f[x] == x ? x : f[x] = Find(f[x]); }
inline void solve() {
int flag = 0;
int q; cin >> n >> q;
iota(f, f + (n * 2) + 1, 0);
for (int i = 0; i < q; ++i) {
int x, y; cin >> x >> y;
if (flag) continue;
/** Find x & y. */
x = Find(x), y = Find(y);
/** Union. */
int a = Find(x + n), b = Find(y + n);
if (x != y) f[a] = y, f[b] = x;
else flag = 1;
}
cout << "Scenario #" << ++t << ":\n";
if (!flag) cout << "No suspicious bugs found!" << '\n';
else cout << "Suspicious bugs found!" << '\n';
}
bool rt = true;
signed main () {
ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
#ifndef ONLINE_JUDGE
freopen("test.in", "r", stdin);
#endif
if (rt) { int T; cin >> T; while(T--) solve(); }
else solve();
return (0 ^ 0);
}
2. 超市
并查集优化
AC代码
/**
* \link: https://www.acwing.com/problem/content/147/
* \category: 二叉堆 并查集 贪心
*
*
* \note:
**/
#include <algorithm>
#include <array>
#include <iostream>
#include <string>
#include <utility>
#include <vector>
#include <numeric>
// #define ONLINE_JUDGE
using namespace std;
using PI = pair<int, int>;
const int kN = 1e4 + 233;
array<int, kN> f;
vector<PI> a;
/** 并查集 */
int Find(int x) { return x == f[x] ? x : f[x] = Find(f[x]); } // ! 并查集注意 返回 f[x] = Find(f[x]) !
/** 贪心 + 并查集优化 */
inline void solve() {
a.reserve(kN);
int n; while (cin >> n) {
a.clear();
int time_max = 0;
int input_value, input_time;
for (int i = 1; i <= n; ++i) {
cin >> input_value >> input_time;
a.push_back({-input_value, input_time});
time_max = max(time_max, input_time);
}
sort(a.begin(), a.end());
iota(f.begin(), next(f.begin(), time_max + 1), 0);
int ans = 0;
for (auto it : a) {
input_value = -it.first, input_time = it.second;
int pos = Find(input_time);
if (pos) {
ans += input_value;
f[pos] = pos - 1;
}
}
cout << ans << '\n';
}
return ;
}
bool rt = false;
int main() {
ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
#ifndef ONLINE_JUDGE
freopen("test.in", "r", stdin);
#endif
if (rt) { int T; cin >> T; while(T--) solve(); }
else solve();
return (0 ^ 0);
}
3. 松鼠排序
题目描述
松鼠宝宝有一排n个大小不一的坚果,松鼠宝宝想把坚果从小到大排序,每次他会选择两个坚果a和b每次花费1点力气把这两个坚果交换,爱动脑筋的松鼠宝宝想知道他排完这n个坚果一共需要花费的最少力气是多少?
输入描述:
第一行一个整数n代表坚果数
接下来一行n个整数代表每个坚果的大小(每个坚果大小都不一样,即大小为1-n的一个排列)
1<=n<=1e5
坚果大小x,1<=x<=n
输出描述:
一行输出代表松鼠宝宝花费的最小力气
输入
3
3 2 1
输出
1
AC代码
/**
* \link:
* \category:
*
* \date:
* \author: YaeSaraki
**/
#include <algorithm>
#include <iostream>
#include <vector>
#include <numeric>
using namespace std;
vector<int> f;
/** using 并查集. */
inline void solve2() {
int n; cin >> n;
/** init. */
vector<int> a(n + 1);
f = a;
iota(f.begin(), f.end(), 0);
for (int i = 1; i <= n; ++i) {
cin >> a.at(i);
f.at(a.at(i)) = i;
}
int ans = 0;
for (int i = 1; i <= n; ++i) {
if (a.at(i) != i) {
int t = f.at(i);
swap(a.at(i), a.at(t));
f.at(a.at(t)) = t;
++ans;
}
}
cout << ans << '\n';
}
/** compressed version. */
inline void solve() {
int n; cin >> n;
vector<int> a(n + 1), b(n + 1);
for (int i = 1; i <= n; ++i) {
cin >> a.at(i);
b.at(a.at(i)) = i;
}
int energy = 0;
for (int i = 1; i <= n; ++i) {
while (a.at(i) != i) {
swap(a.at(i), a.at(a.at(i)));
++energy;
}
}
cout << energy << '\n';
return ;
}
bool rt = false;
signed main() {
ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
#ifndef ONLINE_JUDGE
freopen("test.in", "r", stdin);
#endif
if (rt) { int T; cin >> T; while (T--) solve(); }
else solve2();
return (0 ^ 0);
}
4. 食物链
题目描述
动物王国中有三类动物 A,B,C,这三类动物的食物链构成了有趣的环形。
A 吃 B,B 吃 C,C 吃 A。
现有 N 个动物,以 1∼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,以一个空格分隔。
以下 K 行每行是三个正整数 D,X,Y,两数之间用一个空格隔开,其中 D 表示说法的种类。
若 D=1,则表示 X 和 Y 是同类。
若 D=2,则表示 X 吃 Y。
输出格式
只有一个整数,表示假话的数目。
数据范围
1≤N≤50000,
0≤K≤100000
输入样例:
100 7
1 101 1
2 1 2
2 2 3
2 3 3
1 1 3
2 3 1
1 5 5
输出样例:
3
AC代码
/**
* @problem: 240. 食物链
* @link: https://www.acwing.com/problem/content/242/
* @category: 并查集
* @date:
* @Author: YaeSaraki
**/
#include <algorithm>
#include <iostream>
#include <vector>
#include <numeric>
#define ALL(v) v.begin(), v.end()
#define DBG(x) std::cout << #x << " = " << (x) << '\n'
//#define int long long
using ll = long long;
using PI = std::pair<int, int>;
int lies;
/** 1 -> n 为主视角, 1 -> n 为同类,n + 1 -> 2n 为猎物, 2n + 1 -> 3n为天敌 */
std::vector<int> f;
int Find(int x) { return x == f[x] ? x : f[x] = Find(f[x]); }
bool Union(int x, int y) {
if (x == y) return false;
int a = Find(x), b = Find(y);
f[a] = b;
return true;
}
inline void solve() {
int n, k; std::cin >> n >> k;
f = std::vector<int> (3 * n + 1);
std::iota(ALL(f), 0);
while (k--) {
int op, x, y; std::cin>> op >> x >> y;
/* 如果 x 或者 y 大于 动物总数, 则是谎言 */
if (x > n || y > n) { ++lies;
continue; }
switch(op) {
case 1 : {
/* 如果 x 的 猎物 是 y 或 x 的 天敌 是 y, 则是谎言 */
if (Find(x + n) == Find(y) || Find(x + 2 * n) == Find(y)) { ++lies; break; }
/* x 与 y 是 同类 */
Union(x, y);
/* x 的 猎物 是 y 的 猎物 */
Union(x + n, y + n);
/* x 的 天敌 是 y 的 天敌 */
Union(x + 2 * n, y + 2 * n);
break;
}
case 2 : {
/* 如果 x 与 y 是 同类 或 x 的 天敌 是 y, 则是谎言 */
if (Find(x) == Find(y) || Find(x + 2 * n) == Find(y)) { ++lies; break; }
/* x 的 猎物 是 y */
Union(x + n, y);
/* y 的 天敌 是 x */
Union(y + 2 * n, x);
/* y 的 猎物 是 x 的 天敌 */
Union(y + n, x + 2 * n);
break;
}
default: break;
}
}
std::cout << lies << '\n';
}
bool rt = false;
signed main() {
std::ios::sync_with_stdio(false); std::cin.tie(nullptr); std::cout.tie(nullptr);
#ifndef ONLINE_JUDGE
freopen("test.in", "r", stdin);
#endif
if (rt) { int T; std::cin >> T; while (T--) solve(); }
else solve();
return (0 ^ 0);
}