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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步