[学习笔记] 启发式合并
一、启发式合并
启发式合并多用于合并两个集合,现在有这样一个问题:
现在给定 \(n\) 个集合,第 \(i\) 个集合初始只有 \(\{i\}\),要支持集合的合并操作。
如果我们暴力合并,时间复杂度会是 \(O(n^2)\) 的。
参考并查集的按秩合并,考虑将小的集合合并到大的集合上。
考虑计算时间复杂度,容易发现 \(x+y \geq 2\min(x,y)\),所以集合大小可以是以更小的集合的大小的 \(2\) 倍增长的,所以每个元素至多被操作 \(\log n\) 次,总时间复杂度 \(O(n \log n)\)。
二、洛谷 P3201 [HNOI2009] 梦幻布丁
https://www.luogu.com.cn/problem/P3201
考虑使用 std :: set
维护每个颜色的编号。
因为要进行启发式合并,所以我们要改变一些原有颜色表示的实际颜色,用 \(fa_u\) 表示 \(u\) 对应的当前颜色。
然后进行启发式合并,动态维护相邻两数颜色不同的即可,答案即为相邻颜色不同的数量加 \(1\)。
时间复杂度是 \(O(n \log^2 n)\) 的。
#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>
#include <ext/pb_ds/hash_policy.hpp>
using namespace std;
using namespace __gnu_pbds;
#ifdef LOCAL
#include "algo/debug.h"
#else
#define debug(...) 42
#endif
typedef long long ll;
typedef pair < int, int > PII;
typedef int itn;
mt19937 RND_MAKER (chrono :: steady_clock :: now ().time_since_epoch ().count ());
inline ll randomly (const ll l, const ll r) {return (RND_MAKER () ^ (1ull << 63)) % (r - l + 1) + l;}
bool Memory_Begins;
const double pi = acos (-1);
//__gnu_pbds :: tree < Key, Mapped, Cmp_Fn = std :: less < Key >, Tag = rb_tree_tag, Node_Upadte = null_tree_node_update, Allocator = std :: allocator < char > > ;
//__gnu_pbds :: tree < PPS, __gnu_pbds :: null_type, less < PPS >, __gnu_pbds :: rb_tree_tag, __gnu_pbds :: tree_order_statistics_node_update > tr;
template < class Z >
inline void read (Z &tmp) {
Z x = 0, f = 0;
char c = getchar ();
for ( ; c < '0' | c > '9' ; c = getchar ()) f |= (c == '-');
for ( ; c >= '0' && c <= '9' ; c = getchar ()) x = (x << 1) + (x << 3) + (c & 15);
tmp = !f ? x : -x;
}
const int N = 1e5 + 5, M = 1e6 + 5;
int n, m, a[N], fa[M];
set < int > st[M];
set < int > id;
inline int found (int x) {
return fa[x] == x ? x : fa[x] = found (fa[x]);
}
bool Memory_Ends;
signed main () {
fprintf (stderr, "%.3lf MB\n", (&Memory_Begins - &Memory_Ends) / 1048576.0);
read (n), read (m);
for (int i = 0;i < M; ++ i) fa[i] = i;
for (int i = 1;i <= n; ++ i) {
read (a[i]);
st[a[i]].insert (i);
if (i > 1 && a[i] != a[i - 1]) id.insert (i - 1);
}
while (m --) {
int op, x, y;
read (op);
if (op == 1) {
read (x), read (y);
if (x == y) continue;
if (st[fa[x]].size () > st[fa[y]].size ()) swap (fa[x], fa[y]);
x = fa[x], y = fa[y];
for (int u : st[x]) {
if (id.count (u - 1)) id.erase (u - 1);
if (id.count (u)) id.erase (u);
}
for (int u : st[x]) st[y].insert (u);
for (int u : st[x]) {
if (!st[y].count (u - 1) && st[y].count (u) && u - 1 > 0) id.insert (u - 1);
if (!st[y].count (u + 1) && st[y].count (u) && u < n) id.insert (u);
}
st[x].clear ();
}
else {
printf ("%d\n", id.size () + 1);
}
}
fprintf (stderr, "%.3lf ms\n", 1e3 * clock () / CLOCKS_PER_SEC);
return 0;
}
/*
Things to check:
1. int ? long long ? unsigned int ? unsigned long long ?
2. array size ? is it enough ?
*/
三、P9168 [省选联考 2023] 人员调度 (\(48pts\) 部分分)
https://www.luogu.com.cn/problem/P9168
哎,后悔赛前不学启发式合并......
设可重集 \(st_i\) 表示以 \(i\) 为根的子树每个有人的节点的 \(v\) 的最大值组成的可重集。
显然 \(st_u\) 就是 \(u\) 节点对应集合和 \(u\) 的儿子对应集合的并,留下前 \(siz_u\) 大,\(siz_u\) 表示以 \(u\) 为根的子树的大小。
答案即为 \(\displaystyle \sum_{w \in st_1} w\)。
用 std :: set
维护,可以做到 \(O(nm \log^2 n)\)。
#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>
#include <ext/pb_ds/hash_policy.hpp>
using namespace std;
using namespace __gnu_pbds;
#ifdef LOCAL
#include "algo/debug.h"
#else
#define debug(...) 42
#endif
typedef long long ll;
typedef pair < int, int > PII;
typedef int itn;
mt19937 RND_MAKER (chrono :: steady_clock :: now ().time_since_epoch ().count ());
inline ll randomly (const ll l, const ll r) {return (RND_MAKER () ^ (1ull << 63)) % (r - l + 1) + l;}
bool Memory_Begins;
const double pi = acos (-1);
//__gnu_pbds :: tree < Key, Mapped, Cmp_Fn = std :: less < Key >, Tag = rb_tree_tag, Node_Upadte = null_tree_node_update, Allocator = std :: allocator < char > > ;
//__gnu_pbds :: tree < PPS, __gnu_pbds :: null_type, less < PPS >, __gnu_pbds :: rb_tree_tag, __gnu_pbds :: tree_order_statistics_node_update > tr;
template < class Z >
inline void read (Z &tmp) {
Z x = 0, f = 0;
char c = getchar ();
for ( ; c < '0' | c > '9' ; c = getchar ()) f |= (c == '-');
for ( ; c >= '0' && c <= '9' ; c = getchar ()) x = (x << 1) + (x << 3) + (c & 15);
tmp = !f ? x : -x;
}
const int N = 1e5 + 5;
int n, k, m, p[N], idx[N << 1], idv[N << 1], cnt, F[N], sub[N];
vector < int > G[N];
multiset < int > st[N];
multiset < int > lab[N];
inline void pre (int x) {
sub[x] = 1;
for (int v : G[x]) {
pre (v);
sub[x] += sub[v];
}
}
inline void dfs (int u) {
for (int v : G[u]) dfs (v);
for (int v : G[u]) {
if (lab[F[u]].size () >= lab[F[v]].size ()) {
for (int x : lab[F[v]]) lab[F[u]].insert (x);
}
else {
for (int x : lab[F[u]]) lab[F[v]].insert (x);
F[u] = F[v];
}
}
while (lab[F[u]].size () > sub[u]) {
int mn = *lab[F[u]].begin ();
lab[F[u]].erase (lab[F[u]].find (mn));
}
}
inline ll query () {
for (int i = 1;i <= n; ++ i) lab[i] = st[i], F[i] = i;
dfs (1);
ll ans = 0;
for (int w : lab[F[1]]) ans += 1ll * w;
return ans;
}
vector < ll > ans;
bool Memory_Ends;
signed main () {
// freopen ("transfer6.in", "r", stdin);
// freopen ("transfer6.out", "w", stdout);
// fprintf (stderr, "%.3lf MB\n", (&Memory_Begins - &Memory_Ends) / 1048576.0);
int sid; read (sid);
read (n), read (k), read (m);
for (int i = 1;i <= n; ++ i) G[i].clear ();
for (int i = 2;i <= n; ++ i) read (p[i]), G[p[i]].push_back (i);
pre (1);
for (int i = 1;i <= k; ++ i) {
int x, v; read (x), read (v);
st[x].insert (v);
idx[++ cnt] = x;
idv[cnt] = v;
}
ans.clear ();
ans.push_back (query ());
while (m --) {
int op; read (op);
if (op == 1) {
int x, v; read (x), read (v);
st[x].insert (v);
idx[++ cnt] = x;
idv[cnt] = v;
}
else {
int id; read (id);
int X = idx[id], V = idv[id];
st[X].erase (V);
}
ans.push_back (query ());
}
for (int i = 0;i < ans.size (); ++ i) {
if (i + 1 < ans.size ()) printf ("%lld ", ans[i]);
else printf ("%lld\n", ans[i]);
}
// fprintf (stderr, "%.3lf ms\n", 1e3 * clock () / CLOCKS_PER_SEC);
return 0;
}
/*
Things to check:
1. int ? long long ? unsigned int ? unsigned long long ?
2. array size ? is it enough ?
*/
四、P5290 [十二省联考 2019] 春节十二响
https://www.luogu.com.cn/problem/P5290
考虑解决以 \(u\) 为根的子树内的分段。
设可重集 \(st_u\) 表示以 \(u\) 为根的子树分段的大小,考虑合并 \(st_u\) 和 \(st_v\):
- \(st_u\) 和 \(st_v\) 分别的最大值,次大值,第 \(3\) 大值,...,第 \(\min(|st_u|, |st_v|)\) 大值分别取较大的,与剩余的数组成可重集(删掉\(st_u\) 和 \(st_v\) 分别的最大值,次大值,第 \(3\) 大值,...,第 \(\min(|st_u|, |st_v|)\) 大值)。
最好使用 std :: priority_queue
实现,当然 std :: set
也是可以的,时间复杂度 \(O(n \log^2 n)\)。
#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>
#include <ext/pb_ds/hash_policy.hpp>
using namespace std;
using namespace __gnu_pbds;
#ifdef LOCAL
#include "algo/debug.h"
#else
#define debug(...) 42
#endif
typedef long long ll;
typedef pair < int, int > PII;
typedef int itn;
mt19937 RND_MAKER (chrono :: steady_clock :: now ().time_since_epoch ().count ());
inline ll randomly (const ll l, const ll r) {return (RND_MAKER () ^ (1ull << 63)) % (r - l + 1) + l;}
bool Memory_Begins;
const double pi = acos (-1);
//__gnu_pbds :: tree < Key, Mapped, Cmp_Fn = std :: less < Key >, Tag = rb_tree_tag, Node_Upadte = null_tree_node_update, Allocator = std :: allocator < char > > ;
//__gnu_pbds :: tree < PPS, __gnu_pbds :: null_type, less < PPS >, __gnu_pbds :: rb_tree_tag, __gnu_pbds :: tree_order_statistics_node_update > tr;
template < class Z >
inline void read (Z &tmp) {
Z x = 0, f = 0;
char c = getchar ();
for ( ; c < '0' || c > '9' ; c = getchar ()) f |= (c == '-');
for ( ; c >= '0' && c <= '9' ; c = getchar ()) x = (x << 1) + (x << 3) + (c & 15);
tmp = !f ? x : -x;
}
const int N = 2e5 + 5;
int n, memo[N], fa[N];
vector < int > son[N];
priority_queue < int, vector < int >, less < int > > st[N];
inline void dfs (int u) {
for (int v : son[u]) dfs (v);
for (int v : son[u]) {
if (st[u].size () < st[v].size ()) st[u].swap (st[v]);
vector < int > oth; oth.clear ();
while (!st[v].empty ()) {
oth.push_back (max (st[u].top (), st[v].top ()));
st[u].pop (), st[v].pop ();
}
for (int x : oth) st[u].push (x);
}
st[u].push (memo[u]);
}
bool Memory_Ends;
signed main () {
fprintf (stderr, "%.3lf MB\n", (&Memory_Begins - &Memory_Ends) / 1048576.0);
read (n);
for (int i = 1;i <= n; ++ i) read (memo[i]);
for (int i = 2;i <= n; ++ i) read (fa[i]), son[fa[i]].push_back (i);
dfs (1);
ll ans = 0;
while (!st[1].empty ()) {
ans += 1ll * st[1].top ();
st[1].pop ();
}
printf ("%lld\n", ans);
fprintf (stderr, "%.3lf ms\n", 1e3 * clock () / CLOCKS_PER_SEC);
return 0;
}
/*
Things to check:
1. int ? long long ? unsigned int ? unsigned long long ?
2. array size ? is it enough ?
*/