2025.02.22 CW 模拟赛 C. 环上合并

C. 环上合并

zcy 讲的好.

思路

先考虑一下特殊性质 \(a_i \le a_{i + 1}\). 动手模拟一下可以发现, 对于第 \(2 \sim n - 1\) 个数, 我们需要使用 \(n - (\)该数出现次数\()\) 次操作; 而对于第 1 个和第 \(n\) 个数, 则需要 \(n - (\)该数出现次数\() + 1\) 次操作, 为什么会加一?

举个例子, 对于序列 \(\{2, 1, 3\}\) 且当前 \(k = 2\), 我们发现并不能使用 2 次操作就将序列全部变成 2, 而是需要先对于 1 取 \(\max\) 变成 3, 然后再对两个 3 分别取 \(\min\) 即可\((\)也有其他方法\()\), 这是因为 2 既不是这三个数里的最大值, 也不是最小值.

可以发现我们其实只关心数之间的相对大小关系, 所以我们钦定 \(< k\) 的为 0, \(> k\) 的为 1, \(= k\) 的为 2. 那么上述的性质转化一下就是对于一段极长的 0, 1 交替出现的段, 我们需要使用 \(\lfloor \frac{\textrm{len}}{2} \rfloor\) 次额外操作来将整段变为 \(k\).

现在问题转化成了如何快速对于每一个 \(k\) 求出额外的操作次数. 不难发现这使用线段树比较好维护. 具体来说, 我们对于每一个节点的左右两端分别记 \(w\) 表示最左 / 右端的数 \((\) 0, 1, 2 \()\) 是多少, 再记 \(len\) 表示以最左 / 右端为端点的最长的 0, 1 交替出现的段的长度. 对于整个区间我们记一个 \(sum\) 表示整个区间需要额外操作的次数.

考虑如何合并标记.

先看左区间右端点权值 = 右区间左端点权值的情况. 这种情况比较好合并, 因为左右两段互不相干, 我们直接赋值即可.

接下来说说左区间右端点权值 = 右区间左端点权值 \(\oplus 1\) 的情况. 可以发现, 对于左区间的 \(len_r\) 和右区间的 \(len_l\) 我们可以进行合并, 但代价怎么计算? 其实很简单, 我们只需要减去原有贡献再加上合并起来的贡献即可. 需要特判一下左 / 右区间一整段都是 0, 1 交替出现的段的情况.

最后考虑统计答案. 套路的, 由于我们合并了标记, 答案就是根节点的 \(sum\), 吗? 可能不是, 因为原序列其实是一个, 也就是说根节点的 \(len_l\)\(len_r\) 或许还可以合并, 同上述合并标记的第二种情况.

在实现上, 最开始每个数都应该是 1. 再在枚举 \(k\) 的时候, 我们动态地单点更新每一个需要更新的数即可. 由于每一个数只会被更新至多 2 次, 总时间复杂度 \(\mathcal{O}(m + n \log n)\).

复制代码
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
struct Tree { int lenl, lenr, wl, wr, sum = 0; } tr[N << 2]; void pushup(int u, int l, int r) { if (tr[ls].wr == (tr[rs].wl ^ 1)) { int t = tr[ls].sum + tr[rs].sum - (tr[ls].lenr / 2 + tr[rs].lenl / 2) + (tr[ls].lenr + tr[rs].lenl) / 2; tr[u].sum = t; if (tr[ls].lenl == mid - l + 1) tr[u].lenl = tr[ls].lenl + tr[rs].lenl; else tr[u].lenl = tr[ls].lenl; if (tr[rs].lenr == r - mid) tr[u].lenr = tr[rs].lenr + tr[ls].lenr; else tr[u].lenr = tr[rs].lenr; } else { tr[u].lenl = tr[ls].lenl; tr[u].lenr = tr[rs].lenr; tr[u].sum = tr[ls].sum + tr[rs].sum; } tr[u].wl = tr[ls].wl, tr[u].wr = tr[rs].wr; } void build(int u = 1, int l = 1, int r = n) { if (l == r) { tr[u].lenl = tr[u].lenr = tr[u].wl = tr[u].wr = 1; return; } build(lson), build(rson); pushup(u, l, r); } void update(int x, int k, int u = 1, int l = 1, int r = n) { if (l == r) { tr[u].lenl = tr[u].lenr = k != 2; tr[u].wl = tr[u].wr = k; return; } if (x <= mid) update(x, k, lson); else update(x, k, rson); pushup(u, l, r); } int query() { int ans = tr[1].sum; if (tr[1].lenl == n) return ans; if (tr[1].wl == (tr[1].wr ^ 1)) ans = ans - (tr[1].lenl / 2 + tr[1].lenr / 2) + (tr[1].lenl + tr[1].lenr) / 2; return ans; }
posted @   Steven1013  阅读(2)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
展开