[CSP-S2019] Emiya 家今天的饭

洛咕

题意:原题面见链接,简单来说就是给出一个\(n*m\)的矩阵,每一行代表同一种烹饪方法,每一列代表同一种食材,\(a_{i,j}\)表示使用第i种烹饪方法第j种食材能做出多少种菜,要求至少做一道菜,而且每种烹饪方法至多用一次,并且每种食材不超过总菜数的一半,问有多少种做菜的方案?

分析:直观来讲就是在矩阵上选数,每一行要么不选数要么选一个数,每一列选的数不超过所有选数的一半。

如果只考虑前两个限制条件,设\(g[i][j]\)表示前i行选了j个数的方案数,则\(g[i][j]=g[i-1][j]+g[i-1][j-1]*sum[i]\),其中\(sum[i]\)表示第i行所有数(菜的种类)的和,则总方案数为\(\sum_{j=1}^{n}g[n][j]\)

再考虑第三个限制条件,拿上面所求的总方案数减掉其中不合法的方案数(不满足第三个限制条件的),对于第\(k\)列,设\(f[i][j]\)表示前i行选的数中,在第k列的总数减去不在第k列的总数为j的情况下 的方案数,则\(f[i][j]=f[i-1][j]+f[i-1][j-1]*a[i][k]+f[i-1][j+1]*(sum[i]-a[i][k])\),注意\(j\)的范围应该是\([-n,n]\),所以可以统一右移\(n\)个单位,则不满足第三个限制条件的方案数为\(\sum_{k=1}^{m}\sum_{j=n+1}^{2n}f[n][j]\)

#include<bits/stdc++.h>
using namespace std;
#define ll long long
inline int read(){
    int x=0,o=1;char ch=getchar();
    while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
    if(ch=='-')o=-1,ch=getchar();
    while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
    return x*o;
}
const int N=105;
const int M=2005;
const int mod=998244353; 
int a[N][M],sum[N],g[N][N],f[N][N*2];
int main(){
    int n=read(),m=read();
    for(int i=1;i<=n;++i){
    	for(int j=1;j<=m;++j){
    		a[i][j]=read();
    		sum[i]=(sum[i]+a[i][j])%mod;
		}
	}
	g[0][0]=1;
	for(int i=1;i<=n;++i){
		g[i][0]=1;
		for(int j=1;j<=i;++j){
			g[i][j]=g[i-1][j]+(1ll*g[i-1][j-1]*sum[i])%mod;
			g[i][j]%=mod;
		}
	}
	ll tot1=0,tot2=0;
	for(int j=1;j<=n;++j)tot1=(tot1+g[n][j])%mod;
	for(int k=1;k<=m;++k){
        memset(f,0,sizeof(f));
        f[0][n]=1;//f[0][0]=1
        for(int i=1;i<=n;++i)
            for(int j=n-i;j<=n+i;++j){
            	f[i][j]=f[i-1][j];
            	f[i][j]=(f[i][j]+(1ll*f[i-1][j-1]*a[i][k])%mod)%mod;
            	f[i][j]=(f[i][j]+(1ll*f[i-1][j+1]*(sum[i]-a[i][k]))%mod)%mod;
			}
        for(int j=n+1;j<=n*2;++j)tot2=(tot2+f[n][j])%mod;
	}
	cout<<((tot1-tot2)%mod+mod)%mod<<endl;
    return 0;
}

posted on 2023-03-03 19:11  PPXppx  阅读(12)  评论(0编辑  收藏  举报