P5664[CSP-S2019] Emiya 家今天的饭 (dp + 计数)

2022/9/5 23:25:39

本文主要是介绍P5664[CSP-S2019] Emiya 家今天的饭 (dp + 计数),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

 

P5664[CSP-S2019] Emiya 家今天的饭 (dp + 计数)

题目传送门

题目大意:

给定一个大小为 \(n * m\) 的表格 , 其中 \(a_{i , j}\) 表示用第 \(i\) 种烹饪方式并且有第 \(j\) 种主要食材的不同菜品的数量,找出至少有一种菜品,每种菜品的烹饪方式不同且满足,所有的主要食材的出现次数不超过总菜品数的一半的方案数。

题目分析:
  • [\(1\)] : 因为每种菜品的烹饪方式均不同,那么我们想到可以枚举烹饪方式。

  • [\(2\)] : 正难则反。我们对于方案数可以很快速的求出总的方案数,那我么可以想到用总的方案数减去不符合的方案数。

  • [\(3\)] : 设总方案数为 \(ans\) , 那么我们先考虑每一行的方案数,对于每一行只能选取一种,故每行方案数为这种烹饪方式所能做的所有菜品,我们令 \(sum_i\) 为每行的总数量,那么 \(ans\) 是否等于 \(\prod_{i = 1} ^ n sum_i\) 呢? 答案是否定的,因为对于每一行我们还可以不选,所以 \(ans = \prod_{i = 1} ^ n ({sum_i +1})\) , 在 \(ans\) 中还包含了一个什么也不选的方案,所以还需要将 \(ans\) 减 \(1\)。

  • [\(4\)] : 考虑完第一个以及第二个条件,我们再来考虑不满足第三个条件的方案数,由于遍历有一定的顺序,所以我们考虑使用 \(dp\) 来求解 。

  • [\(5\)] : 首先我们考虑最暴力的做法,根据什么不好处理就将其放到状态中的方法, 我们令 \(f_{i , j, k , l}\) 为前 \(i\) 中烹饪方式中,选了 \(k\) 个菜品 , \(j\) 食材出现了 \(l\) 次的方案数。 那么很好得出: \(f_{i,j,k,l} = f_{i - 1 , j,k,l} + a_{i,j} * f_{i - 1,j,k - 1,l - 1} + (\Sigma_{x=1}^n a_{i,x} - a_{i,j}) * f_{i - 1,j,k - 1,l}\)。 其中 \(\Sigma_{x=1}^n a_{i,x} - a_{i,j}\) 可以变为 \(sum_i - a_{i,j}\)。所以 : \(f_{i,j,k,l} = f_{i - 1 , j,k,l} + a_{i,j} * f_{i - 1,j,k - 1,l - 1} + (sum_i - a_{i,j}) * f_{i - 1,j,k - 1,l}\)
    复杂度为 \(O(n^3m)\)

  • [\(6\)] : 根据转移式子我们可以发现,注意的复杂度已经降为 \(O(1)\) ,故我们考虑去优化状态,因为题目中要求最多的食材不超过所选菜品的一半,所以我们直接维护这种食材与其他食材的差即可 ,所以我们令 \(f_{i,j,k}\) 为前 \(i\) 中烹饪方式,\(j\) 食材的数量与其他所有食材的数量之差为 \(k\)。 那么转移方程为: \(f_{i,j,k} = f_{i - 1,j,k} + a_{i,j} * f_{i - 1,j,k - 1} + (sum_i - a_{i,j}) * f_{i - 1,j,k + 1}\),我们又发现每次转移时 \(j\) 是不发生变化的,所以我们考虑将 \(j\) 这一维去掉,我们可以在最外层枚举第 \(j\) 种食材,然后就降维了,最后我们只需要让 \(ans\) 见到所有 \(k > 0\) 的情况即可。

代码实现:

因为 \(k\) 可能会出现负的,所以我们让 \(k\) 都去加上 \(n\) 来保证 \(k\) 都是正的,其他细节见代码:

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int mod = 998244353;
const int M = 2e3 + 7;
int a[200][M] , sum[200];
int n , m; 
int dp[200][500];
int get_id(int x) {//这里就是将 k 变为正的的
	return x + n + 1;
}
signed main () {
	ios::sync_with_stdio(0),cin.tie(0);
	cin >> n >> m;
	for(int i = 1; i <= n; ++ i) for(int j = 1; j <= m; ++ j) {cin >> a[i][j];sum[i] += a[i][j];sum[i] %= mod;}
	int ans = 1;
	for(int i = 1; i <= n; ++ i) ans = (ans * (sum[i] + 1)) % mod;//求出总方案数
	ans = (ans - 1 + mod) % mod;//减掉一个都不选的
	for(int i = 1; i <= m; ++ i) {
		memset(dp , 0 , sizeof dp);
		dp[0][get_id(0)] = 1;//初值
		for(int j = 1; j <= n; ++ j) 
			for(int k = -1 * n; k <= n; ++ k) 
				dp[j][get_id(k)] = (dp[j - 1][get_id(k)] + a[j][i] * dp[j - 1][get_id(k - 1)] % mod + ((sum[j] + mod - a[j][i]) * dp[j - 1][get_id(k + 1)]) % mod) % mod;
		for(int k = 1; k <= n; ++ k) ans = (ans - dp[n][get_id(k)] + mod) % mod;
	}
	cout << ans;
	return 0;	
}

[========]

完结!



这篇关于P5664[CSP-S2019] Emiya 家今天的饭 (dp + 计数)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程