QOJ 1251 Even rain / Parzysty deszcz 题解
毛营经典的先想题解后出题。
对于最终的积水题解,我们去左右找 \(\max h_i\),即为:
\[\sum_{k = 1}^{n} \min \{ \max_{1 \leq j \leq k} h_i, \max_{k \leq j \leq n} h_j \} - h_k
\]
考虑 DP,记 \(f(i, j, p, s, 0/1)\) 表示前 \(i\) 个数,抹平了 \(j\) 个柱子,前缀 \(\max h\) 为 \(p\),后缀 \(\max h\) 为 \(q\),且当前积水体积 \(\bmod 2 = 0/1\)。一个重要的性质是 \(p, q\) 的取值上限都为 \(k + 1\),状态数为 \(O(nk^3)\) 的。
怎么优化呢?最终,最高位置的柱子必定不会产生积水贡献,我们枚举最高的柱子,左右两边做 DP 然后在最高的位置尝试合并。从左往右、从右往左做两次 DP,可以少计一个 \(q\),将状态表示为 \(f(i, j, p, 0/1)\),复杂度是 \(O(nk^2)\) 的。
具体的转移如下:对于每个位置 \(i\) 我们都有:
- 不抹平:
- 操作次数不变 \(j \leftarrow j\)
- 前缀最大值更新 \(\max\) 原前缀最大值和当前高度
- 奇偶性变化 \(v \oplus (\max(h_i, h_i') - h_i') \bmod 2\),其中 \(h_i\) 为原高度,\(h_i'\) 为当前高度,\(v\) 为原奇偶性
- 抹平
- 操作次数增加 \(j \leftarrow j + 1\)
- 前缀最大值不变
- 奇偶性变化 \(v \oplus (h_i \bmod 2)\)
怎么合并呢?记录左 DP 结果 \(f(i, j, k)\) 表示从左位置 \(i\),使用 \(j\) 次操作,奇偶性为 \(k\);右 DP \(f(i, m - j, p, k)\) 从右到左位置 \(i\),使用 \(m - j\) 次操作,奇偶性为 \(k\),这里的 \(p\) 为对应高度的排名。
合并条件是简单的,\(j + (m - j) = m\),且 \(k = 0\) 且 \(i\) 在左右都取 \(\max\)。
好用的思维确实挺多的?最多操作 \(k\) 次,所以只关心前 \(k + 1\) 大的高度;我们只关心奇偶性不关心具体数值,所以只使用奇偶性区分计数。
高度要离散化,还有滚动数组。
#include <bits/stdc++.h>
using i64 = long long;
constexpr int N = 3e4 + 7;
constexpr int P = 1e9 + 7;
int n, m, ans;
int a[N], b[N], mx[2][30], c[N];
int f[2][30][30][2], g[N][30][2];
std::pair<int, int> p[N];
void modadd(int &x, int y) { (x + y >= P ? x = x + y - P : x = x + y); }
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cin >> n >> m;
for (int i = 1; i <= n; i++) {
std::cin >> a[i];
p[i] = {a[i], i};
}
std::sort(p + 1, p + n + 1);
for (int i = 1; i <= n; i++) {
b[p[i].second] = i;
}
f[0][0][m + 1][0] = 1;
for (int i = 1; i <= n; i++) { // 从左到右
for (int j = 1; j <= m + 1; j++) {
mx[i & 1][j] = mx[(i - 1) & 1][j];
}
for (int j = 1; j <= m + 1; j++) {
if (b[i] > b[mx[i & 1][j]]) {
for (int t = m + 1; t > j; t--) {
mx[i & 1][t] = mx[i & 1][t - 1];
}
mx[i & 1][j] = i;
break;
}
}
for (int j = 1; j <= m + 1; j++) {
c[b[mx[i & 1][j]]] = j;
}
for (int j = 0; j <= m; j++) {
for (int k = 1; k <= m + 1; k++) {
for (int l = 0; l < 2; l++) {
int &x = f[(i - 1) & 1][j][k][l];
if (!x) {
continue;
}
// 不抹
int npos = c[std::max(b[mx[(i - 1) & 1][k]], b[i])];
int nv = (std::max(a[mx[(i - 1) & 1][k]], a[i]) - a[i]) & 1;
modadd(f[i & 1][j][npos][l ^ nv], x);
// 抹
if (j < m) {
int spos = c[b[mx[(i - 1) & 1][k]]];
int sv = a[mx[(i - 1) & 1][k]] & 1;
modadd(f[i & 1][j + 1][spos][l ^ sv], x);
}
x = 0;
}
}
}
for (int j = 0; j <= m; j++) {
for (int k = 0; k < 2; k++) {
modadd(g[i][j][k], f[i & 1][j][c[b[i]]][k]);
}
}
for (int j = 1; j <= m + 1; j++) {
c[b[mx[i & 1][j]]] = 0;
}
}
for (int i = 0; i <= m; i++) { // 从右到左
for (int k = 1; k <= m + 1; k++) {
for (int l = 0; l < 2; l++) {
f[n & 1][i][k][l] = 0;
}
}
}
for (int j = 1; j <= m + 1; j++) {
mx[(n + 1) & 1][j] = 0;
}
f[(n + 1) & 1][0][m + 1][0] = 1;
for (int i = n; i >= 1; i--) {
for (int j = 1; j <= m + 1; j++) {
mx[i & 1][j] = mx[(i + 1) & 1][j];
}
for (int j = 1; j <= m + 1; j++) {
if (b[i] > b[mx[i & 1][j]]) {
for (int t = m + 1; t > j; t--) {
mx[i & 1][t] = mx[i & 1][t - 1];
}
mx[i & 1][j] = i;
break;
}
}
for (int j = 1; j <= m + 1; j++) {
c[b[mx[i & 1][j]]] = j;
}
for (int j = 0; j <= m; j++) {
for (int k = 1; k <= m + 1; k++) {
for (int l = 0; l < 2; l++) {
int &x = f[(i + 1) & 1][j][k][l];
if (!x) {
continue;
}
// 不抹
int npos = c[std::max(b[mx[(i + 1) & 1][k]], b[i])];
int nv = (std::max(a[mx[(i + 1) & 1][k]], a[i]) - a[i]) & 1;
modadd(f[i & 1][j][npos][l ^ nv], x);
// 抹
if (j < m) {
int spos = c[b[mx[(i + 1) & 1][k]]];
int sv = a[mx[(i + 1) & 1][k]] & 1;
modadd(f[i & 1][j + 1][spos][l ^ sv], x);
}
x = 0;
}
}
}
for (int j = 0; j <= m; j++) {
for (int k = 0; k < 2; k++) {
modadd(ans, (i64) g[i][j][k] * f[i & 1][m - j][c[b[i]]][k] % P);
}
}
for (int j = 1; j <= m + 1; j++) {
c[b[mx[i & 1][j]]] = 0;
}
}
std::cout << ans << "\n";
return 0;
}

浙公网安备 33010602011771号