CSP 2019 Day2 T1 Emiya 家今天的饭

题目链接

solution:

快要考试了,虚了,开始写去年的题的题解了
注意到每种主要食材至多在一半的菜(即 \(\lfloor \frac{k}{2} \rfloor\)道菜)中被使用
这个东西显然不好求,于是考虑补集转化
即在满足第二个条件下,总方案数减去其中某一列选择超过一半的方案数
选择超过一半的列有且仅有一列且每一列互不影响
于是可以枚举每一列后分别\(dp\)
\(f_{i,j,k}\)表示现在枚举到第\(col\)列第\(i\)行时当前列选择了\(j\)个,其他列选择了\(k\)个的方案数
那么有如下转移

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

其中\(s_i\)表示第i行所有数的和
相当于在当前行决策:不选,选且选当前列,选且不选当前列
当前列总方案数就是

\[\sum_{j>k}f_{n,j,k} \]

考虑如何优化
发现我们并不需要知道当前列和其他列的具体选择了多少个
我们只要知道它们相差多少就可以转移了
\(f_{i,j}\)表示现在枚举到第\(col\)列第\(i\)行时当前列比其他列多选了\(j\)个的方案数
有类似转移:

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

当前列总不合法方案数为

\[\sum_{j>0}f_{n,j} \]

那么如何求总方案数呢?
\(g_{i,j}\)表示考虑前\(i\)行恰好选择\(j\)个的方案数
那么

\[g_{i,j}=g_{i-1,j}+s_i*g_{i-1,j-1} \]

然后就可以过了

time complexity:

\(O(n^2m)\)

code:

#include<bits/stdc++.h>
using namespace std;
const int N=105,M=2005,mod=998244353;
int f[N][N<<1],g[N][N],n,m,a[N][M],s[N],ans;
inline int add(int x,int y){x+=y;return x>=mod?x-mod:x;}
inline int dec(int x,int y){x-=y;return x<0?x+mod:x;}
inline int mul(int x,int y){return 1ll*x*y%mod;}
int main()
{
	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]);
	g[0][0]=1;
	for(int i=1;i<=n;++i)
		for(int j=0;j<=i;++j)
			g[i][j]=add(g[i-1][j],mul(s[i],g[i-1][j-1]));
	for(int i=0;i<=n;++i)ans=add(ans,g[n][i]);
	for(int col=1;col<=m;++col)
	{
		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]=add(f[i-1][j],add(mul(a[i][col],f[i-1][j-1]),mul(dec(s[i],a[i][col]),f[i-1][j+1])));
		for(int i=1;i<=n;++i)ans=dec(ans,f[n][n+i]);
	}
	printf("%d\n",dec(ans,1));
	return 0;
}
posted @ 2020-11-03 11:10  BILL666  阅读(66)  评论(0编辑  收藏  举报