「解题报告」AGC021F Trinity

组合意义天地灭。

观察题意,肯定是从 \(A\) 入手。发现,\(A\) 的作用其实就是限制每一行强制选 \(A_i\) 的位置,且可以选 \(A_i\) 往后的位置。那么我们从左往右考虑,每次确定若干个 \(A_i\) 放这一列。这时候根据放的位置与之前确定的行的数量,是可以计算出当前这一列放的最大值与最小值的方案数的。

假如我们当前在第 \(k\) 列,这一列之前已经确定了 \(i\) 行。

那么如果这一列不放,那么这 \(i\) 行可以随便选,方案数为 \(\binom{i+1}{2} + 1\)(还有一个不选的方案数)。

如果这一列放,枚举放了 \(j\) 行,那么我们现在相当于有这样一个问题:有 \(i\) 个白球与 \(j\) 个黑球随机排列,并且要在最左边的黑球往左选 \(0/1\) 个白球染成黑色,将最右边的黑球往右选 \(0/1\) 个白球染称黑色,求方案数。我们在左右再加一个白球,就变成恰好染一个了,这相当于将 \(i\) 个白球中插入 \(j + 2\) 个黑球,方案数为 \(\binom{i + j + 2}{j + 2}\)。(用终止状态推起始状态)

那么此时我们就可以 DP 了,设 \(f_{i, k}\) 表示 \(k\) 列确定 \(i\) 行的方案数,转移式子 \(f_{i, k} \times \binom{i + j + 2}{j + 2} \to f_{i + j, k + 1}\),可以拆成卷积形式优化,复杂度 \(O(nm \log n)\)

int f[MAXN][MAXN];
int main() {
    scanf("%d%d", &n, &m);
    init(10000);
    f[0][0] = 1;
    for (int i = 1; i <= m; i++) {
        Polynomial a(n), b(n);
        for (int j = 0; j <= n; j++) {
            a[j] = 1ll * f[i - 1][j] * inv[j] % P;
            b[j] = (j != 0) * inv[j + 2];
        }
        a = a * b;
        for (int j = 0; j <= n; j++) {
            f[i][j] = 1ll * a[j] * fac[j + 2] % P;
            f[i][j] = (f[i][j] + 1ll * f[i - 1][j] * (1ll * j * (j + 1) / 2 % P + 1)) % P;
        }
    }
    int ans = 0;
    for (int i = 0; i <= n; i++) {
        ans = (ans + 1ll * C(n, i) * f[m][i]) % P;
    }
    printf("%d\n", ans);
    return 0;
}
posted @ 2023-04-14 07:06  APJifengc  阅读(33)  评论(0编辑  收藏  举报