专栏文章

题解:P5664 [CSP-S2019] Emiya 家今天的饭

P5664题解参与者 1已保存评论 0

文章操作

快速查看文章及其快照的属性,并进行相关操作。

当前评论
0 条
当前快照
1 份
快照标识符
@miqnsxod
此快照首次捕获于
2025/12/04 07:49
3 个月前
此快照最后确认于
2025/12/04 07:49
3 个月前
查看原文

题目大意

给你一个 n×mn\times m 的矩阵 aaai,ja_{i,j} 表示在第 ii 种烹饪方式下用第 jj 种原料能做出 ai,ja_{i,j} 道菜,然后给你三个条件。
设 Emiya 会做 kk 道菜。
  • 会做至少一道菜,即 k1k \geqslant 1
  • 每道菜的烹饪方法互不相同
  • 每种主要食材至多在一半的菜(即 k2\lfloor \frac{k}{2} \rfloor 道菜)中被使用。
求出方案数对 998,244,353998,244,353 取模的结果。

解题思路

首先分析条件。
  • 会做至少一道菜
这个没啥好说的。
  • 每道菜的烹饪方法互不相同
即每行只能选一个菜。
  • 每种主要食材至多在一半的菜中被使用。
即每一列选的菜的个数不超过一半的菜。
这是本题的重点。
我们可以考虑容斥,用总方案数减去任意一列选的菜数大于总菜数的一半的方案数就是答案了,接下来分开考虑。

总方案数

sumi=j=1mai,jsum_i=\sum^m_{j=1}a_{i,j}
因为每一行只能选一个或不选,所以第 ii 行的方案数为 sumi+1sum_i+1,但是必须至少做一道菜,所以 ans=(i=1nsumi)1ans=(\prod^{n}_{i=1}sum_i)-1
CPP
long long o=1;
for(int i=1;i<=n;i++){
    for(int j=1;j<=m;j++){
      cin>>a[i][j];
      sum[i]=(sum[i]+a[i][j])%mod;
    }
    o=o*(sum[i]+1)%mod;
}
o=(o-1+mod)%mod;

任意一列选的菜数大于总菜数的一半的方案数

有点绕。
kk 为烹饪的的总菜数。
如果有一个原料选的菜数大于 k2\lfloor\frac{k}{2}\rfloor,那么其他的菜便不可能大于 k2\lfloor\frac{k}{2}\rfloor
所以我们可以枚举是哪一个原料做的菜大于 k2\lfloor\frac{k}{2}\rfloor 了。
我们发现我们只关心这个原料做的菜是否大于 k2\lfloor\frac{k}{2}\rfloor,而不关心到底做了多少个。
所以考虑差值 dp,对于食材 cc ,设 fi,jf_{i,j} 表示考虑了前 ii 个烹饪方法,第 cc 个食材做了多少个菜与其他食材做了多少个菜的差为 jj 的方案数。
由于第 cc 个食材的菜数大于 k2\lfloor\frac{k}{2}\rfloor,所以如果 j>0j>0,那么就代表第 cc 个食材的菜数大于 k2\lfloor\frac{k}{2}\rfloor 了。(感性理解)
考虑怎么从 i1i-1 转移到 ii
首先如果第 ii 种烹饪方式不选,那么 jj 不变,就有 fi1,jfi,jf_{i-1,j}\Rightarrow f_{i,j}
如果选了第 cc 种原材料,那么 jj 就会加一,但是你就算选第 cc 种原材料也有 ai,ca_{i,c} 种选法,于是有 fi1,j1×ai,cfi,jf_{i-1,j-1}\times a_{i,c}\Rightarrow f_{i,j}
如果没选第 cc 种原材料,那么 jj 就会减一,会有 sumiai,csum_i-a_{i,c} 种选法,于是有 fi1,j+1×(sumiai,c)fi,jf_{i-1,j+1}\times (sum_i-a_{i,c})\Rightarrow f_{i,j}
所以转移方程为:
fi,j=fi1,j+fi1,j1×ai,c+fi1,j+1×(sumiai,c)f_{i,j}=f_{i-1,j}+f_{i-1,j-1}\times a_{i,c}+f_{i-1,j+1}\times (sum_i-a_{i,c})
但是又由于 jj 有可能是负数,所以我们整体加上 nn 就不会数组越界了。

code

CPP
#include<bits/stdc++.h>
#define mod 998244353
using namespace std;
long long n,m,ans;
long long f[101][301];
long long sum[101];
long long g[201][201];
long long a[201][2001];
int main()
{
	cin>>n>>m;
	long long o=1;//总方案数
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			cin>>a[i][j];
			sum[i]=(sum[i]+a[i][j])%mod;
		}
		o=o*(sum[i]+1)%mod;
	}
	o=(o-1+mod)%mod;
	for(int ch=1;ch<=m;ch++)
	{
		memset(f,0,sizeof(f));//每一个原材料单独求
		f[0][n]=1;//全体加n
		for(int i=1;i<=n;i++)
		{
			for(int j=n-i;j<=n+i;j++)//在i时最少会到n-i,最多会到n+i
			{
				f[i][j]=(f[i-1][j]+f[i-1][j-1]*a[i][ch]%mod+f[i-1][j+1]*(sum[i]+mod-a[i][ch])%mod)%mod;
			}
		}
		for(int i=1;i<=n;i++)//统计答案
		{
			ans=(ans+f[n][i+n])%mod;
		}
	}
	cout<<(o-ans+mod)%mod;//容斥
	return 0;
}

评论

0 条评论,欢迎与作者交流。

正在加载评论...