QOJ 1251 Even rain / Parzysty deszcz 题解

Link

毛营经典的先想题解后出题。

对于最终的积水题解,我们去左右找 \(\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\) 我们都有:

  1. 不抹平:
    • 操作次数不变 \(j \leftarrow j\)
    • 前缀最大值更新 \(\max\) 原前缀最大值和当前高度
    • 奇偶性变化 \(v \oplus (\max(h_i, h_i') - h_i') \bmod 2\),其中 \(h_i\) 为原高度,\(h_i'\) 为当前高度,\(v\) 为原奇偶性
  2. 抹平
    • 操作次数增加 \(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;
}
posted @ 2025-11-12 15:57  夢回路  阅读(7)  评论(0)    收藏  举报