CSP2019 Emiya 家今天的饭

题目链接

参考题解

Solution

每一列仅当取数超过 \(\lfloor {\frac{k}{2}} \rfloor\) 时不合法,所以最多只有一列不合法。考虑容斥,用总方案数 - 不合法方案数。

\(g_{i,j}\) 表示前 i 行选了 j 个数的方案数,\(s_i\) 表示第 i 行的总和,转移:\(g_{i,j}=g_{i-1,j}+g_{i-1,j-1}*s_i\)

枚举不合法的列为 \(col\),设 \(f_{i,j,k}\) 表示前 \(i\) 行,当前列选了 \(j\) 个数,其他列选了 \(k\) 个数的方案数,分为三种情况:

1.当前行什么都不选 \(f_{i,j,k}\) += \(f_{i-1,j,k}\)

2.当前行在当前列选 1 个数 \(f_{i,j,k}\) += \(f_{i-1,j-1,k} * a_{i,col}\)

3.当前行在其他列选 1 个数 \(f_{i,j,k}\) += \(f_{i-1,j,k-1} * (s_i - a_{i,col})\)

综上,\(f_{i,j,k}=f_{i-1,j,k}+f_{i-1,j-1,k}*a_{i,col}+f_{i-1,j,k-1}*(s_i-a_{i,col})\)

这样做的时间复杂度是 \(O(mn^3)\),可以得到 84 分。

考虑到我们根本不关心 \(j, k\) 的实际大小,而是只关心它们的相对大小,重新定义状态 \(f_{i,j}\) 表示前 i 行,当前列和其他列的差值为 \(j\)。转移方程:

\(f_{i,j}=f_{i-1,j}+f_{i-1,j-1}*a_{i,col}+f_{i-1,j+1}*(s_i-a_{i,col})\)

为了防止爆数组,\(j\) 这一维 + n。复杂度 \(O(2n^2m)\)

Code

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define LL long long
using namespace std;

const LL N = 1100, mod = 998244353;
LL n, m, ans = 0, a[666][2333], f[N][N], s[N], g[N][N];

int main()
{
    scanf("%lld%lld", &n, &m);
    memset(s, 0, sizeof(s));
    for(LL i = 1; i <= n; i++)
        for(LL j = 1; j <= m; j++)
            scanf("%lld", &a[i][j]), s[i] = (s[i] + a[i][j]) % mod;
    g[0][0] = 1;
    for(LL i = 1; i <= n; i++)
    for(LL j = 0; j <= n; j++)
    g[i][j] = (g[i - 1][j] + g[i - 1][j - 1] * s[i]) % mod;
    for(LL i = 1; i <= n; i++) ans = (ans + g[n][i] + mod) % mod;
    for(LL col = 1; col <= m; col++)
    {
        f[0][n] = 1;
        for(LL i = 1; i <= n; i++)
        for(LL j = 1; j <= n * 2; j++)
        f[i][j] = (f[i - 1][j] + a[i][col] * f[i - 1][j - 1]) % mod, 
        f[i][j] = (f[i][j] + (s[i] - a[i][col]) * f[i - 1][j + 1]) % mod;
        for(LL i = n + 1; i <= 2 * n; i++)
        ans = ((ans - f[n][i]) % mod + mod) % mod;
    }
    printf("%lld", ans % mod);
    return 0;
}
posted @ 2020-08-30 09:15  Nyxia  阅读(89)  评论(0编辑  收藏  举报