启发式合并的例题
启发式合并是将小的部分合并到大的部分,比直接合并更能降低时间复杂度,比较符合直觉
基本的一些会使用到的数组与映射关系
\(home\) : 物品的虚拟编号
\(id\) : 集合虚拟编号对应的实际编号
\(pos\) : 集合实际编号对应的虚拟编号
一些转换 :
物品 \(i\) 实际所在的集合下标(即虚拟编号) = \(home[i]\)
物品 \(i\) 所在集合的实际编号 = \(id[home[i]]\)
集合 \(j\) 的实际位置 = \(pos[j]\)
虚拟编号对应的实际编号 \(j = id[pos[j]]\) (也就是说 \(pos\) 和 \(id\) 是双向映射)
梦幻布丁
将集合映射到一个虚拟编号(pos),之后在合并的过程前,先判断一下改变颜色是否会使段数减少,即改变为的颜色有与即将改变的相邻位置
点击查看代码
```cpp
#include <bits/stdc++.h>
using namespace std;
set<int> s[1000010];
int pos[1000010], home[100010];
void init() {
for (int i = 1; i <= 1e6; i ++) {
pos[i] = i;
}
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
init();
int n, q;
cin >> n >> q;
for (int i = 1; i <= n; i ++) {
cin >> home[i];
s[home[i]].insert(i);
}
int sum = n;
for (int i = 1; i < n; i ++) {
if (home[i] == home[i + 1]) sum --;
}
int op, x, y;
while (q --) {
// cout << q << endl;
cin >> op;
if (op == 1) cin >> x >> y;
if (op == 1) {
if (x == y) continue;
if (s[pos[x]].size() > s[pos[y]].size()) {
swap(pos[x], pos[y]);
}
int u = pos[x], v = pos[y];
for (auto &i : s[u]) {
sum -= (home[i + 1] == v) + (home[i - 1] == v);
}
for (auto &i : s[u]) {
s[v].insert(i);
home[i] = v;
}
s[u].clear();
} else {
cout << sum << '\n';
}
}
}
基本上,这道题运用了几种启发式合并的以上所有映射关系
当然,我也看到一种类似 dsu on tree 的方法,是树上启发式合并的一种运用方式,但不如这种双向映射的方法好理解
Sample Input
1
5 10
4 3
1 2 4
1 1 3
4 4
1 5 5
4 5
3 1 2
4 4
3 5 2
4 3
Sample Output
3
2
5
1
5
建一个双向的映射, 把实际的编号映射到我们虚拟的编号, 同时也能把虚拟的编号反向映射到实际的编号,既可以用set,也可以用并查集
合并操作,把小的集合并入大的集合,并修改映射,不超过 \(O(nlogn)\)
单点修改,直接修改虚拟编号,并将其移到另一个集合
交换操作,直接交换映射关系
查询操作,利用虚拟编号找到实际编号
点击查看代码
```cpp
#include <bits/stdc++.h>
using namespace std;
const int N = 1000010;
void solve() {
int n, q;
cin >> n >> q;
vector<int> home(n + 1), pos(n + 1), id(n + 1);
vector<set<int>> s(n + 1);
for (int i = 1; i <= n; i ++) {
s[i].insert(i);
home[i] = pos[i] = id[i] = i;
}
int op, a, b;
while (q --) {
cin >> op >> a;
if (op < 4) cin >> b;
if (op == 1) {
int u = pos[a], v = pos[b];
if (s[u].size() < s[v].size()) {
for (auto &i : s[u]) {
s[v].insert(i);
home[i] = v;
}
s[u].clear();
swap(pos[a], pos[b]);
swap(id[u], id[v]);
} else {
for (auto &i : s[v]) {
s[u].insert(i);
home[i] = u;
}
s[v].clear();
}
} else if (op == 2) {
s[home[a]].erase(a);
s[pos[b]].insert(a);
home[a] = pos[b];
} else if (op == 3) {
swap(pos[a], pos[b]);
swap(id[pos[a]], id[pos[b]]);
} else {
cout << id[home[a]] << '\n';
}
}
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t --) {
solve();
}
return 0;
}