[学习笔记] 启发式合并

一、启发式合并

启发式合并多用于合并两个集合,现在有这样一个问题:

现在给定 \(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 ?
*/

image

四、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 ?
*/
posted @ 2023-07-09 09:28  CountingGroup  阅读(83)  评论(0编辑  收藏  举报