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

你怎么知道我 vp 虽然被 B 硬控了 50min 但最后在 1:59:50 绝杀 D2 了。(

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

考虑对于 \(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\) 层具体的分法,到阶段结束时再考虑计算贡献。
具体来说对于转移,考虑分为两种:

  • \(k + 1 < c\),那么直接飞一个纸飞机即可 \(f_{i, j, k}\to f_{i + 1, j, k + 1}\)

  • \(k + 1 = c\),那么之后 \(j\) 这一层就不能飞纸飞机了,考虑枚举 \(j\) 这一层在之前飞了多少个纸飞机。

    具体来说,记 \(1\sim i\)\(a_{i'} > j\) 的数量为 \(c_1\)\(a_{i'} = j\) 的数量为 \(c_2\),那么还有 \(k - c_1 - c_2\) 个纸飞机没有确定,枚举 \(x\in [c_2. k - c_1]\) 表示 \(j\) 层的纸飞机数,转移为 \(\binom{k - c_1 - c_2}{x - c_2}f_{i, j, k}\to f_{i + 1, j + 1, k - x}\)

    需要注意可能 \(j\) 这一层飞出去了 \(0\) 个纸飞机,所以 \(j + 1\sim n\) 的纸飞机数仍为 \(k\),要继续删掉 \(j + 1\) 层。

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

\(k + 1 < c\) 时状态数 \(\mathcal{O}(nmc)\) 转移 \(\mathcal{O}(1)\)\(k + 1 = c\) 时状态数 \(\mathcal{O}(nm)\) 转移 \(\mathcal{O}(c)\),所以总时间复杂度为 \(\mathcal{O}(nmc)\)

#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;
}
posted @ 2025-07-10 18:52  rizynvu  阅读(28)  评论(0)    收藏  举报