P8978 「DTOI-4」中位数

· · 题解

很不错的题目,思路很自然,结论较难证但好猜,思维含量很高。

首先二分答案转化为 01 序列,目标是将所有数全部变成 1

基本性质:若可操作 I_1, I_2I_1\subset I_2,则操作 I_2 一定更优。

设最优操作序列为 I_1, I_2, \cdots, I_e

1 的权值为 10 的权值为 -1,前缀和序列为 s,每次可将权值为正的一段区间全部变成 1。设操作 I 时其权值为 v(I)

性质 1:对于 1\leq i < ev(I_i) = 1

证明:设 v(I_i) > 1,则 I_i 向左或向右扩展 1 一定不会使得 v(I_i)\leq 0

\square

性质 1 的推论:

性质 2:若存在方案使得 [1, n] 全为 1,则 e\leq \lceil \log_2 n\rceil

证明:若初始存在 o(I) \geq 2v(I) > 0,即存在 10111 作为子串,则总存在操作序列,使得 o(I_{i + 1}) \geq |I_i| = 2o(I_i) - 1,易知 o(I_i) \geq 2 ^ {i - 1} + 1,则 e\leq \lceil \log_2 n\rceil

若初始不存在这样的 I,即无法操作长度大于 1 的区间,则无法对序列产生任何影响,即无解。

\square

性质 3:对于任意 1\leq i < j \leq eI_i\subseteq I_jI_j \subseteq I_i

证明

先证明 相交必然包含:考虑两次相交但不包含的操作 I_i = [a, c]I_j = [b, d],其中 a < b \leq c < d,则令 I_j = [a, d] 一定合法,因为此时 [a, b) 均为 1

接下来只需证明 一定相交。我们使用调整法。

找到最后两个相邻且不交的区间 I_j, I_{j + 1},则 I_{j + 1}\subset I_{j + 2} \subset \cdots \subset I_e。因为 I_e = [1, n],所以存在 j + 1\leq p < e 使得 I_p\cup I_j = \varnothing,且 I_{p + 1} \cup I_j = I_j

|I_j| \geq |I_p|,我们可以将 I_{j + 1}\sim I_p 全部取消,替换为 I_j 的 “超区间” I_j\subset I\subseteq I_{p + 1},这样消去 0 的数量为 |I_j| - 1\geq |I_p| - 1,而 I_{j + 1}\sim I_p 消去的 0 的数量显然不超过 |I_p| - 2,所以替换后 I_{p + 1} 的权值一定增加,而操作数不增。

|I_j| < |I_p|,类似地,取消操作 I_j,替换为在 I_p 后插入 I_p 的 “超区间” I_p\subset I\subseteq I_{p + 1}I_{p + 1} 的权值增加,而操作数不变。

调整后,相邻且不交的区间的位置单调递减,因此调整总可以结束。

\square

有了性质 2 和性质 3,我们可以设计 DP f_{i, l, r} 表示 I_i = [l, r] 是否可行。转移暴力枚举扩展区间,单次检查时间复杂度 \mathcal{O}(n ^ 4\log n)

根据基本性质,对于每个 l,我们只关心最大的 r 使得 f_{i, l, r} = 1。因此,考虑 值域定义域互换 的套路,设 f_{i, l} 表示从 l 开始,最大的 r 使得原 f_{i, l, r} 等于 1

再根据基本性质,如果 p < qf_{i, p} \geq f_{i, q},那么 f_{i, q} 是无用的。因此,从 f_{i - 1} \to f_i 时,我们只关心所有不被包含的 I_l = [l, f_{i - 1, l}] 区间,即左右端点单调递增。

c(I) 表示操作 I = [l, r] 后相对于原序列,对包含 I 的区间的权值产生的贡献,即 r - l + 1 - (s_r - s_{l - 1})。考虑检查 f_{i, l} 能否为 p,那么我们求出所有使得 [j, f_{i - 1, j}]\subseteq [l, p]c([j, f_{i - 1, j}]) 的最大值 c_{\max},那么 s_p - s_{l - 1} 加上 c_{\max} 之后应为正数,即 s_{l - 1} - c_{\max} < s_p

这样,我们有了单次检查 \mathcal{O}(n ^ 2\log n) 的做法。结合递增性质,换成扫描线 + 单调栈 + 线段树二分即可 \mathcal{O}(n\log ^ 2 n),但总复杂度 \mathcal{O}(n \log ^ 3 n) 及大常数仍无法通过。

我们进一步利用递增性质,从后往前扫描线。加入区间 I_l 时,权值小于 c(I_l) 的区间就无用了。这启发我们使用单调队列,从队头到队尾 区间位置从右往左,同时 权值递减。我们希望找到队列中的第一个位置 I_p,使得存在 q \geq f_{i - 1, p}s_{l - 1} - c(I_p) < s_q。预处理 g_i 表示使得 s_q \geq i 的最大的 q,则相当于检查是否有 g(s_{l - 1} - c(I_p) + 1)\geq f_{i - 1, p}

看了下标算,定义是相同的,换了一种转移方式:求值的极值而不是求位置的极值。感觉麻烦了点。 总时间复杂度 $\mathcal{O}(n\log ^ 2 n)$。 ```cpp #include <bits/stdc++.h> using namespace std; constexpr int N = 4e5 + 5; int n, k, a[N], b[N], s[N]; int f[N], tag[N], buc[N << 1]; bool check(int x) { for(int i = 1; i <= n; i++) { s[i] = s[i - 1] + (a[i] >= x ? 1 : -1); } memset(tag, 0, sizeof(tag)); for(int i = 1, lim = N; i <= n; i++) { if(s[i - 1] < lim) lim = s[i - 1], tag[i] = 1; } memset(buc, 0, n + 2 << 3); for(int i = 1; i <= n; i++) buc[n + s[i]] = i; for(int i = n * 2 - 1; ~i; i--) buc[i] = max(buc[i], buc[i + 1]); for(int i = 1; i <= n; i++) f[i] = i - 1; for(int _ = 1; _ <= k && (1 << _ - 1) <= n; _++) { static int d[N], val[N], hd, tl; hd = 1, tl = 0; for(int i = n; i; i--) { if(!tag[i]) continue; int v = f[i] - i + 1 - (s[f[i]] - s[i - 1]); while(hd <= tl && v >= val[tl]) tl--; val[++tl] = v, d[tl] = f[i]; while(hd <= tl) { int v = val[hd], p = buc[max(0, s[i - 1] + 1 - v + n)]; if(p >= d[hd]) {f[i] = p; break;} hd++; } } if(f[1] == n) return _ <= k; } return 0; } int main() { ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); cin >> n >> k; for(int i = 1; i <= n; i++) cin >> a[i], b[i] = a[i]; sort(b + 1, b + n + 1); int c = unique(b + 1, b + n + 1) - b - 1; int l = 1, r = c; while(l < r) { int m = l + r + 2 >> 1; if(check(b[m])) l = m; else r = m - 1; } cout << b[l] << "\n"; return 0; } ```