CSP-S2 Emiya 家今天的饭

题目描述

CSP-S2 Emiya 家今天的饭_第1张图片
CSP-S2 Emiya 家今天的饭_第2张图片

题目分析

  • 这一道题本身算法并不难,但思考难度也不低。
  • “烹饪方法互不相同”这个条件用DP可以很好解决,关键是每一个食材出现次数不超过 k 2 \frac{k}{2} 2k接下来我们所说的不合法均指不满足该条件
  • 本题最关键的一点便是——容斥。考虑先求出总的方案数,再减去不合法的方案数。
  • 总数求法很简单,那么如何求不合法的方案数呢?我们考虑先枚举超出限制的食材。因为超限的食材会出现超过 k 2 \frac{k}{2} 2k次,所以不可能同时有两个食材超出限制,这是解决这个问题的突破口。
  • 接着我们就可以设DP了,朴素的做法是设 f [ i ] [ j ] [ k ] f[i][j][k] f[i][j][k]表示当前DP做到第 i i i种烹饪方法,有 j j j道菜不用食材 x x x k k k道菜用了食材 x x x,这样的情况有多少种方案。其中食材 x x x指的是我们所枚举的超限的食材。
  • 这样的话我们的答案就是满足 j < k jj<k f [ n ] [ j ] [ k ] f[n][j][k] f[n][j][k]的和。
  • 但我们发现这样子做的时间复杂度是 O ( m ∗ n 3 ) O(m*n^3) O(mn3)的,那么该如何优化呢?
  • 发现这个DP的关键是 j < k jj<k,移项可得 j − k < 0 j-k<0 jk<0,假如我们把 j − k j-k jk当成状态,是否能优化DP呢?
  • 答案是肯定的。
  • 我们设 f [ i ] [ p ] f[i][p] f[i][p],其中 i i i的意义同上, p p p就是上述的 j − k j-k jk
  • 下面是状态转移方程:
  • f [ i ] [ p ] = { f [ i − 1 ] [ p + 1 ] ∗ ( S u m − a [ i ] [ x ] ) 选 用 食 材 x f [ i − 1 ] [ p − 1 ] ∗ a [ i ] [ x ] 不 选 用 食 材 x f [ i − 1 ] [ p ] 不 用 该 烹 饪 方 法 做 菜 f[i][p]=\begin{cases} f[i-1][p+1]*(Sum-a[i][x])&选用食材x \\f[i-1][p-1]*a[i][x]&不选用食材x \\f[i-1][p]&不用该烹饪方法做菜 \end{cases} f[i][p]=f[i1][p+1](Suma[i][x])f[i1][p1]a[i][x]f[i1][p]xx
  • 最后只要对于 p < 0 p<0 p<0 f [ n ] [ p ] f[n][p] f[n][p]加起来即可,时间复杂度 O ( m ∗ n 2 ) O(m*n^2) O(mn2)

代码

#include
#include
using namespace std;
typedef long long ll;
const int N=110,M=2e3+100;
const int mod=998244353;
int a[N][M],f[N][N],Sum[N],g[N][2*N];
int main()
{
	freopen("meal.in","r",stdin);
	freopen("meal.out","w",stdout);
	int n,m;
	scanf("%d%d",&n,&m);
	memset(Sum,0,sizeof(Sum));
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++){
			scanf("%d",&a[i][j]);
			Sum[i]=(Sum[i]+a[i][j])%mod;
		}
	f[0][0]=1;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			f[i][j]=(f[i-1][j]+(ll)f[i-1][j-1]*Sum[i])%mod;
		}
		f[i][0]=1;
	}
	int Ans=0;
	for(int i=1;i<=n;i++) Ans=(Ans+f[n][i])%mod;
	for(int i=1;i<=m;i++){
		memset(g,0,sizeof(g));
		g[0][n]=1;
		for(int j=1;j<=n;j++){
			for(int k=0;k<=2*n;k++){
				if(g[j-1][k]){
					g[j][k+1]=(g[j][k+1]+(ll)g[j-1][k]*(Sum[j]-a[j][i]+mod))%mod;
					g[j][k-1]=(g[j][k-1]+(ll)g[j-1][k]*a[j][i])%mod;
					g[j][k]=(g[j][k]+g[j-1][k])%mod;
				}
			}
		}
		int sum=0;
		for(int j=0;j<n;j++) sum=(sum+g[n][j])%mod;
		Ans=(Ans-sum+mod)%mod;
	}
	printf("%d\n",Ans);
	return 0;
}

你可能感兴趣的:(比赛,DP)