2025.04.17 CW 模拟赛 A. 稻田灌溉

A. 稻田灌溉

题目描述

小明种了 \(n\) 颗水稻,它们排成一排。

由于这 \(n\) 颗水稻的品种不一样,所以它们需要灌溉的水量也不一样,具体地:第 \(i\) 颗水稻需要被灌溉 \([a_i, b_i]\) 单位之间的水,多了会淹死,少了会旱死。

小明每次可以给一个区间 \([L_i, R_i]\) 浇水,这会使这个区间每颗水稻的水量 +1。

小明决定一共浇水 \(m\) 次。

问:小明有多少种方法浇水,可以使得每一颗水稻都能得到需要的水。


思路

看到 \(n \le 100\) 的数据, 第一眼认为是 \(\mathcal{O}(n^3)\) 的区间 \(\rm{DP}\)​, 但是对于两段相邻的区间不能进行合并, 所以放弃了.

转化一下问题. 我们将每一个区间看做一对括号, 那么每个位置被包含的次数需要在 \([a_i, b_i]\) 内, 求方案数.

接着考虑这样一个 \(\rm{DP}\): 我们令 \(f_{i, j, k}\) 表示当前枚举到第 \(i\) 个位置, 已经使用了 \(j\) 对括号, 当前第 \(i\) 个位置被 \(k\) 对括号包含的方案数. 显然, \(f_{i, j, k}\) 中合法的 \(k \in [a_i, b_i]\).

至于从 \(i - 1\) 进行的转移, 我们枚举 \(t, w\) 分别表示有 \(t\) 对括号从 \(i\) 开头, \(w\) 表示 \(i - 1\) 个位置被 \(w\) 对括号包含, 那么有

\[f_{i, j, k} \gets \sum_{t = 0}^{\min(j, k)} \binom{m - j - t}{t} \times \sum_{w \ge k - t} f_{i - 1, j - t, w} \times \binom{w}{k - t} \]

我们定义「延伸数量」为从 \(i - 1\) 继承到 \(i\) 的括号数量, 亦即不在 \(i - 1\) 结尾的括号数量. 在转移式子中体现为 \(k - t\), 同时因为是在 \(w\) 个里面选择 \(k - t\) 个进行「延伸」, 所以需要乘上系数 \(\binom{w}{k - t}\). 在外侧的 \(\binom{m - j - t}{t}\) 指的是在剩下的 \(m - j - t\) 个里面选择 \(t\) 个从 \(i\) 开头的方案数.

直接转移复杂度是 \(\mathcal{O}(nm^4)\) 的. 不难发现从 \(i - 1\) 转移到 \(i\) 对于每一个 \(t\) 转移的实质上是一段后缀, 预处理即可, 时间复杂度 \(\mathcal{O}(nm^3)\).

#include <iostream>

using namespace std;

constexpr int MOD = 998244353;

int n, m, C[301][301], a[101], b[101], f[101][301][301], sum[101][301][301];
__int128 val;

int add(int x, int y) {
    (x += y) >= MOD and (x -= MOD);
    return x;
}

void addon(int& x, int y) {
    (x += y) >= MOD and (x -= MOD);
}

void init() {
    cin >> n >> m;
    for (int i = 0; i <= m; ++i) {
        C[i][0] = C[i][i] = 1;
        for (int j = 1; j < i; ++j) {
            C[i][j] = add(C[i - 1][j], C[i - 1][j - 1]); 
        }
    }
    for (int i = 1; i <= n; ++i) {
        cin >> a[i];
    }
    for (int i = 1; i <= n; ++i) {
        cin >> b[i];
    }
}

void calculate() {
    f[0][0][0] = sum[0][0][0] = 1;
    for (int i = 1; i <= n; ++i) {
        for (int j = 0; j <= m; ++j) {
            for (int k = a[i]; k <= b[i]; ++k) {
                val = 0;
                for (int t = min(j, k); ~t; --t) {
                    val += 1ll * sum[i - 1][j - t][k - t] * C[j][t];
                }
                addon(f[i][j][k], val % MOD);
            }
            for (int k = 0; k <= b[i + 1]; ++k) {
                val = 0;
                for (int t = b[i]; t >= k; --t) {
                    val += 1ll * f[i][j][t] * C[t][k];
                }
                sum[i][j][k] = val % MOD;
            }
        }
    }
    int ans = 0;
    for (int i = a[n]; i <= b[n]; ++i) {
        addon(ans, f[n][m][i]);
    }
    cout << ans << '\n';
}

void solve() {
    cin.tie(nullptr)->sync_with_stdio(false);
    init();
    calculate();
}

int main() {
    solve();
    return 0;
}
posted @ 2025-04-17 20:15  Steven1013  阅读(15)  评论(0)    收藏  举报