CF 2066D2 Club of Young Aircraft Builders (hard version)

· · 题解

cnblogs。

说一个看起来暴力得多的做法!

考虑对于 i 只有在 i\sim n 层飞出的纸飞机数量 < c 时才能飞一个纸飞机。
那么如果知道了当前每个楼层飞出的纸飞机数,考虑找到最小的 i 满足 i\sim n 层飞出的纸飞机数 < c,此时就相当于是只有 i\sim n 层可以飞出纸飞机。

发现这就天然的分出了阶段:1\sim n 层都可以飞纸飞机,2\sim n 层可以飞纸飞机,一直到最后第 n 层的纸飞机也飞完结束。

于是考虑记 f_{i, j, k} 表示考虑了前 i 个纸飞机,此时 j\sim n 层还可以飞纸飞机,且 j\sim n 层一共飞了 k 个纸飞机的方案数。
为了方便转移,考虑延后计算贡献,就是此时不去区分 j\sim n 层具体的分法,到阶段结束时再考虑计算贡献。
具体来说对于转移,考虑分为两种:

还需要考虑的是,如果遇到了 a_i > 0,说明此时 a_i 一定能够飞纸飞机,所以 j 的合法范围应为 [1, a_i]

```cpp #include <bits/stdc++.h> using ll = long long; constexpr ll mod = 1e9 + 7; constexpr int maxn = 100 + 2; constexpr int maxm = 10000 + 5; ll binom[maxn][maxn]; inline void init() { for (int i = 0; i < maxn; i++) { binom[i][0] = 1; for (int j = 1; j <= i; j++) { binom[i][j] = (binom[i - 1][j - 1] + binom[i - 1][j]) % mod; } } } int a[maxm], b[maxn], sum[maxm][maxn]; ll f[maxn][maxn], g[maxn][maxn]; inline void solve() { int n, c, m; scanf("%d%d%d", &n, &c, &m); for (int i = 1; i <= m; i++) scanf("%d", &a[i]); for (int i = 1; i <= n; i++) { b[i] = std::count(a + 1, a + m + 1, i); if (b[i] > c) { return puts("0"), void(); } } for (int i = 1; i <= m; i++) { memcpy(sum[i], sum[i - 1], sizeof(sum[i])); for (int j = 1; j <= a[i]; j++) { sum[i][j]++; } } memset(f, 0, sizeof(f)), f[1][0] = 1; for (int i = 1; i <= m; i++) { for (int p = 1; p <= (a[i] == 0 ? n : a[i]); p++) { for (int x = c; x >= 1; x--) { f[p][x] = f[p][x - 1]; } f[p][0] = 0; } for (int p = (a[i] == 0 ? n : a[i]) + 1; p <= n + 1; p++) { memset(f[p], 0, sizeof(ll) * (c + 1)); } for (int p = 1; p <= n; p++) { for (int x = b[p]; x <= c - sum[i][p + 1]; x++) { (f[p + 1][c - x] += f[p][c] * binom[c - sum[i][p]][x - b[p]]) %= mod; } f[p][c] = 0; } } printf("%lld\n", f[n + 1][0]); } int main() { init(); int t; scanf("%d", &t); while (t--) solve(); return 0; } ```