【CSP2019】Emiya 家今天的饭
DP,容斥思想
题目大意:
有 $n$ 种烹饪方法和 $m$ 种主要食材。用第 $i$ 种烹饪方法和第 $j$ 种主要食材可以做出 $a_{i,j}$ 道不同的菜。
现在要做一些菜,必须满足以下条件:
- 令 $k$ 表示菜的道数。$k\geq 1$。
- $k$ 道菜的烹饪方法各不相同。
- 同一种主要食材不得出现在超过 $\lfloor\frac k 2\rfloor$ 道菜中。
问方案数。
题解:
直接做不好求。
观察到第三条限制。发现如果存在一种食材出现超过 $\lfloor\frac k 2\rfloor$ 次,那么别的食材一定不会超过。也就是说最多有一种食材的菜不满足条件。
我们先算出不考虑第三条限制的总方案,可以用背包求,$O(n^2)$。然后减掉违反第三条限制的方案数。
我们枚举主要食材,钦定这个食材违反了条件。
我们需要求出这个食材在多少种方案中违反了条件。
令 $f[i][j]$ 表示前 $i$ 种烹饪方法中,选择当前选定的食材比其余食材多 $j$ 的方案数。
三种转移:要么不选这种烹饪方法,要么选择钦定的食材,要么选择钦定以外的食材。
转移显然是 $O(1)$ 的。
需要减去的是 $\sum\limits_{j>0} f[n][j]$,表示这种菜超过了一半。
对每种钦定的菜的时间复杂度是 $O(n^2)$。
总时间复杂度 $O(n^2m)$。
Code:
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int N=105,M=2005,md=998244353;
typedef long long LL;
int n,m,a[N][M];
int ans,all[N];
struct array{
int val[N*2];
inline int&operator[](const int&x){return val[x+103];};
}dp[N];
inline void upd(int&a){a+=a>>31&md;}
void calc_all(){
int dp[N];
memset(dp,0,sizeof dp);
*dp=1;
for(int i=1;i<=n;++i)
for(int j=n;j;--j)
dp[j]=(dp[j]+(LL)all[i]*dp[j-1])%md;
for(int i=1;i<=n;++i)upd(ans+=dp[i]-md);
}
int main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>n>>m;
for(int i=1;i<=n;++i){
int&x=all[i];
for(int j=1;j<=m;++j)
cin>>a[i][j],upd(x+=a[i][j]-md);
}
calc_all();
for(int s=1;s<=m;++s){
memset(dp,0,sizeof dp);
dp[0][0]=1;
for(int i=1;i<=n;++i){
dp[i]=dp[i-1];
for(int j=-i;j<=i;++j)
dp[i][j]=(dp[i][j]+(LL)dp[i-1][j-1]*a[i][s]+(LL)dp[i-1][j+1]*(all[i]-a[i][s]+md))%md;
}
for(int j=1;j<=n;++j)
upd(ans-=dp[n][j]);
}
cout<<ans<<'\n';
return 0;
}