「CSPS 2019」Emiya 家今天的饭

description

loj 3211

solution

看到题目中要求每种主要食材至多在一半的菜中被使用,容易想到补集转换。

\(ans=\)总方案数-存在某一种食材在一半以上的菜中被使用的方案。

总方案数很容易求:即对于每一种烹饪方法选至多一道菜的方案为\(s_i+1\),其中\(s_i=\sum_{j=1}^{m} a_{i,j}\)

故总方案数\(=\prod_{i=1}^{n} (s_i+1)-1\),其中-1是因为要去掉一道菜都没有选的方案。

而不合法的方案,我们可以先钦定第\(w\)列中选出了一半以上的数,于是可以用\(f_{i,j,k}\)表示前\(i\)行中已经在第\(w\)列选了\(j\)个数,在其他列选了\(k\)个数的方案。于是有:

\[f_{i,j,k}=f_{i-1,j,k}+a_{i,w}*f_{i-1,j-1,k}+(s_i-a_{i,w})*f_{i-1,j,k-1} \]

于是答案就是所有满足\(j>k\)\(f_{n,j,k}\)的和,可以$O(mn^3)做。

考虑优化,事实上我们一直只关心\(j-k\)的大小,于是可以将状态改为\(f_{i,j}\)表示前\(i\)行中,第\(w\)列比其他列多选\(j\)个数的方案。于是有:

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

于是答案就是\(\sum_{i=1}^{n} f_{n,i}\),就可以\(O(mn^2)\)完成,可以通过此题。
为了防止出现负数下标,可以将下标整体加\(n\)

code

#include<cstdio>
#include<cstring>
const int mod=998244353;
const int N=210;
int a[N][2010],s[N],tot=1,bad,n,m,f[N][N];
int add(int x,int y){return (x+y>=mod)?x+y-mod:x+y;}
int dec(int x,int y){return (x-y<0)?x-y+mod:x-y;}
int main(){
	freopen("meal.in","r",stdin);
	freopen("meal.out","w",stdout);
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i){
		for(int j=1;j<=m;++j) scanf("%d",&a[i][j]),s[i]=add(s[i],a[i][j]);
		tot=1ll*tot*(s[i]+1)%mod;
	}
	for(int w=1;w<=m;++w){
		std::memset(f,0,sizeof(f));
		f[0][n]=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]=add(f[i][j],1ll*f[i-1][j-1]*a[i][w]%mod);
				f[i][j]=add(f[i][j],1ll*f[i-1][j+1]*dec(s[i],a[i][w])%mod);
			}
		}
		for(int i=n+1;i<=n+n;++i)tot=dec(tot,f[n][i]); 
	}
	printf("%d\n",dec(tot,1));
	return 0;	
}
原文地址:https://www.cnblogs.com/tqxboomzero/p/13919421.html