题解:P5664 [CSP-S2019] Emiya 家今天的饭
题目大意
给你一个
设 Emiya 会做
- 会做至少一道菜,即
k \geqslant 1 。 - 每道菜的烹饪方法互不相同。
- 每种主要食材至多在一半的菜(即
\lfloor \frac{k}{2} \rfloor 道菜)中被使用。
求出方案数对
解题思路
首先分析条件。
- 会做至少一道菜。
这个没啥好说的。
- 每道菜的烹饪方法互不相同。
即每行只能选一个菜。
- 每种主要食材至多在一半的菜中被使用。
即每一列选的菜的个数不超过一半的菜。
这是本题的重点。
我们可以考虑容斥,用总方案数减去任意一列选的菜数大于总菜数的一半的方案数就是答案了,接下来分开考虑。
总方案数
设
因为每一行只能选一个或不选,所以第
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;
任意一列选的菜数大于总菜数的一半的方案数
有点绕。
设
如果有一个原料选的菜数大于
所以我们可以枚举是哪一个原料做的菜大于
我们发现我们只关心这个原料做的菜是否大于
所以考虑差值 dp,对于食材
由于第 (感性理解)
考虑怎么从
首先如果第
如果选了第
如果没选第
所以转移方程为:
但是又由于
code
#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;
}