CSP2019 Emiya家今天的饭 [DP][容斥]

Emiya 家今天的饭

题目描述

Emiya 是个擅长做菜的高中生,他共掌握 \(n\) 种烹饪方法,且会使用 \(m\) 种主要食材做菜。为了方便叙述,我们对烹饪方法从 $1 \sim n $编号,对主要食材从 $1 \sim m $编号。

Emiya 做的每道菜都将使用恰好一种烹饪方法与恰好一种主要食材。更具体地,Emiya 会做 \(a_{i,j}\) 道不同的使用烹饪方法 \(i\) 和主要食材 \(j\) 的菜\((1 \leq i \leq n, 1 \leq j \leq m)\),这也意味着 Emiya 总共会做 \(\sum\limits_{i=1}^{n} \sum\limits_{j=1}^{m}a_{i,j}\)道不同的菜。

Emiya 今天要准备一桌饭招待 Yazid 和 Rin 这对好朋友,然而三个人对菜的搭配有不同的要求,更具体地,对于一种包含 \(k\) 道菜的搭配方案而言:
Emiya 不会让大家饿肚子,所以将做至少一道菜,即 \(k \geq 1\)
Rin 希望品尝不同烹饪方法做出的菜,因此她要求每道菜的烹饪方法互不相同
Yazid 不希望品尝太多同一食材做出的菜,因此他要求每种主要食材至多在一半的菜(即 \(\lfloor \frac{k}{2} \rfloor\) 道菜)中被使用
这里的 \(\lfloor x \rfloor\) 为下取整函数,表示不超过 \(x\) 的最大整数。

这些要求难不倒 Emiya,但他想知道共有多少种不同的符合要求的搭配方案。两种方案不同,当且仅当存在至少一道菜在一种方案中出现,而不在另一种方案中出现。

Emiya 找到了你,请你帮他计算,你只需要告诉他符合所有要求的搭配方案数对质数 \(998,244,353998,244,353\) 取模的结果。

数据范围

对于所有测试点,保证 \(1 \leq n\leq100\)
\(1 \leq m \leq 2000,\)
\(0\leq a_{i,j}\lt 998,244,353。\)

Solution

爆搜二三十分的样子
考虑容斥,先算出没有限制条件的所有情况,然后减去不合法的情况。
所有情况:
\(dp1[i][j]\)表示前\(i\)种烹饪方法共选了\(j\)个食材。则有选和不选两种转移。
\(dp1[i][j] = dp1[i - 1][j] + dp1[i - 1][j - 1] * sum[i]\)
非法情况:
对于每一个食品做一遍dp
考虑状态,因为食品个数小于\(\lfloor \frac{j}{2} \rfloor\)是合法,于是就想到把选了的第t个食品的数量和已经选了的烹饪方法的个数加入状态。
那么\(dp2[i][j][k]\)表示前i行一共选了j种烹饪方法,第t个食品选了k个的方案数
于是非法的方案数就是\(\sum\limits_{k > \lfloor \frac{j}{2} \rfloor}{dp2[i][j][k]}\)
转移有三个:
\(dp2[i][j][k] = dp2[i - 1][j][k] + dp2[i - 1][j - 1][k - 1] * a[i][j] + dp2[i - 1][j - 1][k] * (sum[i] - a[i][j])\)
分别是不做菜,做菜选择第t个食品,做菜没选第t个食品
复杂度\(O(mn^3)\) 84分
考虑优化
其实非法的状况就是某一种食品数量大于其他种类食品的数量和。比如1,2,3三种食品,一共选了3道菜,食品1选了2种,食品2选了1种,2 > 1,该方案非法。
所以记录d =(非法食品数(k) - 合法食品数(j))代替上面的j,k两维,若d>0则非法。
\(dp2[i][d] = dp2[i - 1][d] +dp2[i - 1][d - 1]* a[i][j] + dp2[i - 1][d + 1]* (sum[i] - a[i][j])\)
(注意这个d可能小于0,为防止下标越界可以通通加上一个大数字)
然后就AC了

Code

#include<bits/stdc++.h>
using namespace std;
const long long N = 105,M = 2005,MOD = 998244353;
long long a[N][M],dp1[N][M],dp2[N][M],sum[N];
int n,m;
int main(){
	cin >> n >> m;
	for(int i = 1 ; i <= n; i++)
		for(int j = 1; j <= m; j++){
			scanf("%d",&a[i][j]);
			sum[i] += a[i][j];
			sum[i] %= MOD;
		}
	dp1[0][0] = 1;
	for(int i = 1; i <= n; i++){
		for(int j = 0; j <= i; j++)
		dp1[i][j] = (dp1[i - 1][j] + dp1[i - 1][j - 1] * sum[i]) % MOD; 
	}
	long long b = 0;
	for(int k = 1; k <= m; k++){
		memset(dp2,0,sizeof(dp2));
		dp2[0][100] = 1;
		for(int i = 1; i <= n; i++){
			for(int d = i - 100; d <= i + 100; d++){
				dp2[i][d + 100] = (dp2[i - 1][d + 100] + dp2[i - 1][d - 1 + 100] * a[i][k] + dp2[i - 1][d + 1 + 100] * (sum[i] - a[i][k])) % MOD; 
			}
		}
		for(int i = 101;i <= 100 + n; i++){
			b += dp2[n][i];
			b %= MOD;
		}
	}
	long long ans = 0;
	for(int i = 1; i <= M; i++)
		ans = ans + dp1[n][i];
	cout << (ans - b) % MOD << endl;
	return 0;
} 


借鉴了这位dalao的题解:https://blog.csdn.net/weixin_37517391/article/details/103110646

posted @ 2019-11-24 21:53  foxc  阅读(708)  评论(0编辑  收藏  举报