洛谷 P5664 Emiya 家今天的饭【dp】


题目:

传送门


题意:

有一个 n ∗ m n*m nm的矩阵, ( x , y ) (x,y) (x,y)表示由 x x x方法和 y y y食材做出的菜有多少种
我们可以选择一些菜来制作,但需要满足一些要求:
1. 1. 1.至少做一道菜
2. 2. 2.任意两种菜的方法不能相同
3. 3. 3.用同一食材做出的菜不能超过 n / 2 n/2 n/2
问还有多少种方案


分析:

前两个要求都不是事,但问题在第三个要求,我们貌似直接求的话不好设置状态
正所谓正难则反,我们通过计算不合法的方案和总方案,就可以得到我们希望的答案了
不合法:
因为无论如何,都只会是一种食材不合法,不可能存在两种食材都做出了超过 n / 2 n/2 n/2的菜
所以我们可以把这一个食材拎出来,其他的食材无论如何分配都不会影响到合法性
f i , j f_{i,j} fi,j表示已经考虑到第 i i i种方法,而我们假定不合法这一食材与其他食材做出的菜的差为 j j j,因为每种方法都至多有一道菜,所以转移挺好写的
最后不合法的方案我们就去 j > n / 2 j>n/2 j>n/2里面找
全部:
这多简单呐,莫得任何限制,设 g i , j g_{i,j} gi,j表示考虑到第 i i i种方法,选择了 j j j种方法的方案数
对每种方案的菜品的数目求个和,都可以愉快计算
最后的答案是 ∑ i = 1 n g n , i − ∑ i = n / 2 + 1 n f n , i \sum_{i=1}^ng_{n,i}-\sum_{i=n/2+1}^nf_{n,i} i=1ngn,ii=n/2+1nfn,i


代码:

#include<iostream>
#include<cstdio>
#include<string>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#define LL long long 
#define mo 998244353
using namespace std;
inline LL read()
{
	LL s=0,f=1; char c=getchar();
	while(c<'0'||c>'9') {if(c=='-') f=-1;c=getchar();}
	while(c>='0'&&c<='9') {s=s*10+c-'0';c=getchar();}
	return s*f;
}
LL a[105][2005],s[105][2005];
LL f[105][205],g[105][105];
int main()
{
	LL n=read(),m=read();
	for(LL i=1;i<=n;i++)
	{ 
	  LL sum=0;
	  for(LL j=1;j<=m;j++) (sum+=a[i][j]=read())%=mo;
	  for(LL j=1;j<=m;j++) s[i][j]=(sum-a[i][j]+mo)%mo;
	  s[i][0]=sum; g[i][0]=1;
	}
	LL r=0;
	for(LL e=1;e<=m;e++)
	{
		memset(f,0,sizeof(f));
		f[0][n]=1;
		for(LL i=1;i<=n;i++)
		  for(LL j=n-i;j<=n+i;j++)
		    f[i][j]=(f[i-1][j]+a[i][e]*f[i-1][j-1]%mo+s[i][e]*f[i-1][j+1]%mo)%mo;
		for(LL i=n+1;i<=2*n;i++) (r+=f[n][i])%=mo;
	}
	g[0][0]=1;
	for(LL i=1;i<=n;i++)
	  for(LL j=1;j<=i;j++)
	    g[i][j]=(g[i-1][j]+g[i-1][j-1]*s[i][0]%mo)%mo;
	LL sum=0;
	for(LL i=1;i<=n;i++) (sum+=g[n][i])%=mo;
	cout<<(sum-r+mo)%mo;
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值