启发式合并的例题

启发式合并是将小的部分合并到大的部分,比直接合并更能降低时间复杂度,比较符合直觉


基本的一些会使用到的数组与映射关系

\(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;
}

posted @ 2025-04-02 16:17  he_jie  阅读(14)  评论(0)    收藏  举报