[CSP-S2019] Emiya 家今天的饭 题解
Statement
Solve
可以发现最难处理是第三个限制,即对列的限制
正难则反 我们考虑容斥
\(ans=\) 总方案数-有一列不合法方案书
显然,只可能有一列不合法
——
首先考虑总方案数怎么算
设 \(f[i][j]\) 表示前 \(i\) 行选 \(j\) 个菜的方案数,那么
\[f[i][j]=f[i-1][j]+f[i-1][j-1]*sum[i]
\]
其中,\(sum[i]=\sum_{j=1}^ma[i][j]\)
那么总方案数为 \(\sum_{i=1}^n f[n][i]\) ,初态 \(f[0][0]=1\) 即可
这样的时间是 \(O(nm)\) ,足够了
更快的方法是,\(all=\prod (sum[i]+1)-1\) ,减一是因为不能不选
——
因为只有一列不合法,我们考虑枚举这个列,设它为 \(pos\)
考虑状态怎么设,发现我们只关心 \(pos\) 列选的数量和其他列选的数量,
因此,设 \(g[i][j][k]\) 表示前 \(i\) 行,第 \(pos\) 列选了 \(j\) 次,其他列选了 \(k\) 次的方案数,那么
\[g[i][j][k]=g[i-1][j][k]+g[i-1][j-1][k]*a[i][pos]+g[i-1][j][k-1]*(sum[i]-a[i][j])
\]
即不选/选\(pos\)/选其他
总共不合法方案为 \(\sum_{j>k}g[n][j][k]\)
但这样时间是 \(O(n^3m)\) ,需要优化
由上述计算总不合法的式子,发现我们真正关心的是 \(j,k\) 的大小关系
于是我们可以更改状态,设 \(g[i][j]\) 表示前 \(i\) 行,第 \(pos\) 列比其他列多选了 \(j\) 次的方案数,那么
\[g[i][j]=g[i-1][j]+g[i-1][j-1]*a[i][pos]+g[i-1][j+1]*(sum[i]-a[i][pos])
\]
即不选/选 \(pos\)/选其他
这样,时间就是 \(O(n^2m)\) 的
同时考虑到 \(j\) 可能为负数,所以我们不妨将他整体加上 \(n\)
Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int mod = 998244353;
const int N = 1e2+5;
const int M = 2e3+5;
int a[N][M],f[N][N],g[N][N*2],sum[N];
int n,m,ans;
signed main(){
scanf("%lld%lld",&n,&m);
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
scanf("%lld",&a[i][j]),(sum[i]+=a[i][j])%=mod;
f[0][0]=1;
for(int i=1;i<=n;++i)
for(int j=0;j<=n;++j)
f[i][j]=(f[i-1][j]+(j?f[i-1][j-1]*sum[i]%mod:0))%mod;
for(int i=1;i<=n;++i)
(ans+=f[n][i])%=mod;
for(int pos=1;pos<=m;++pos){
memset(g,0,sizeof(g));
g[0][n]=1;
for(int i=1;i<=n;++i)
for(int j=n-i;j<=n+i;++j)
g[i][j]=(g[i-1][j]+g[i-1][j-1]*a[i][pos]%mod+g[i-1][j+1]*(sum[i]-a[i][pos])%mod)%mod;
for(int i=1;i<=n;++i)
ans=(ans-g[n][i+n]+mod)%mod;
}
printf("%lld\n",ans%mod);
return 0;
}