洛谷 P5664 [CSP-S2019] Emiya 家今天的饭

[CSP-S2019] Emiya 家今天的饭

题目描述

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

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

Emiya 今天要准备一桌饭招待 Yazid 和 Rin 这对好朋友,然而三个人对菜的搭配有不同的要求,更具体地,对于一种包含 k k k 道菜的搭配方案而言:

  • Emiya 不会让大家饿肚子,所以将做至少一道菜,即 k ≥ 1 k \geq 1 k1
  • Rin 希望品尝不同烹饪方法做出的菜,因此她要求每道菜的烹饪方法互不相同
  • Yazid 不希望品尝太多同一食材做出的菜,因此他要求每种主要食材至多在一半的菜(即 ⌊ k 2 ⌋ \lfloor \frac{k}{2} \rfloor 2k 道菜)中被使用

这里的 ⌊ x ⌋ \lfloor x \rfloor x 为下取整函数,表示不超过 x x x 的最大整数。

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

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

输入格式

第 1 行两个用单个空格隔开的整数 n , m n,m n,m

第 2 行至第 n + 1 n + 1 n+1 行,每行 m m m 个用单个空格隔开的整数,其中第 i + 1 i + 1 i+1 行的 m m m 个数依次为 a i , 1 , a i , 2 , ⋯   , a i , m a_{i,1}, a_{i,2}, \cdots, a_{i,m} ai,1,ai,2,,ai,m

输出格式

仅一行一个整数,表示所求方案数对 998 , 244 , 353 998,244,353 998,244,353 取模的结果。

样例 #1

样例输入 #1

2 3 
1 0 1
0 1 1

样例输出 #1

3

样例 #2

样例输入 #2

3 3
1 2 3
4 5 0
6 0 0

样例输出 #2

190

样例 #3

样例输入 #3

5 5
1 0 0 1 1
0 1 0 1 0
1 1 1 1 0
1 0 1 0 1
0 1 1 0 1

样例输出 #3

742

提示

【样例 1 解释】

由于在这个样例中,对于每组 i , j i, j i,j,Emiya 都最多只会做一道菜,因此我们直接通过给出烹饪方法、主要食材的编号来描述一道菜。

符合要求的方案包括:

  • 做一道用烹饪方法 1、主要食材 1 的菜和一道用烹饪方法 2、主要食材 2 的菜
  • 做一道用烹饪方法 1、主要食材 1 的菜和一道用烹饪方法 2、主要食材 3 的菜
  • 做一道用烹饪方法 1、主要食材 3 的菜和一道用烹饪方法 2、主要食材 2 的菜

因此输出结果为 3   m o d   998 , 244 , 353 = 3 3 \bmod 998,244,353 = 3 3mod998,244,353=3。 需要注意的是,所有只包含一道菜的方案都是不符合要求的,因为唯一的主要食材在超过一半的菜中出现,这不满足 Yazid 的要求。

【样例 2 解释】

Emiya 必须至少做 2 道菜。

做 2 道菜的符合要求的方案数为 100。

做 3 道菜的符合要求的方案数为 90。

因此符合要求的方案数为 100 + 90 = 190。

【数据范围】

测试点编号 n = n= n= m = m= m= a i , j < a_{i,j}< ai,j<测试点编号 n = n= n= m = m= m= a i , j < a_{i,j}< ai,j<
1 1 1 2 2 2 2 2 2 2 2 2 7 7 7 10 10 10 2 2 2 1 0 3 10^3 103
2 2 2 2 2 2 3 3 3 2 2 2 8 8 8 10 10 10 3 3 3 1 0 3 10^3 103
3 3 3 5 5 5 2 2 2 2 2 2 9 ∼ 12 9\sim 12 912 40 40 40 2 2 2 1 0 3 10^3 103
4 4 4 5 5 5 3 3 3 2 2 2 13 ∼ 16 13\sim 16 1316 40 40 40 3 3 3 1 0 3 10^3 103
5 5 5 10 10 10 2 2 2 2 2 2 17 ∼ 21 17\sim 21 1721 40 40 40 500 500 500 1 0 3 10^3 103
6 6 6 10 10 10 3 3 3 2 2 2 22 ∼ 25 22\sim 25 2225 100 100 100 2 × 1 0 3 2\times 10^3 2×103 998244353 998244353 998244353

对于所有测试点,保证 1 ≤ n ≤ 100 1 \leq n \leq 100 1n100 1 ≤ m ≤ 2000 1 \leq m \leq 2000 1m2000 0 ≤ a i , j < 998 , 244 , 353 0 \leq a_{i,j} \lt 998,244,353 0ai,j<998,244,353

本题考察的是容斥原理,通过观察发现至多有一列选择的数量超过 ⌊ k 2 ⌋ \lfloor\frac{k}{2}\rfloor 2k,这意味着可以对列进行容斥。
首先枚举不合法的列 c c c,设 f i , j f_{i,j} fi,j 表示前 i i i 行,第 c o l col col 列选择的数量与其他列的差为 j j j 的方案数。
f i , j = f i − 1 , j + f i − 1 , j − 1 ∗ a i , c o l + f i − 1 , j + 1 ∗ ( s i − a i , c o l ) f_{i,j}=f_{i-1,j}+f_{i-1,j-1}*a_{i,col}+f_{i-1,j+1}*(s_i-a_{i,col}) fi,j=fi1,j+fi1,j1ai,col+fi1,j+1(siai,col)
把非法的方案数累加起来 s u b A n s = ∑ c ∑ j > 0 f n , j subAns=\sum_c\sum_{j>0}f_{n,j} subAns=cj>0fn,j
接下来计算总方案数,设 g i , j g_{i,j} gi,j 表示前 i i i 行,选择了 j j j 道菜的方案数。 则 g i , j = g i − 1 , j + g i − 1 , j − 1 ∗ s i g_{i,j}=g_{i-1,j}+g_{i-1,j-1}*s_i gi,j=gi1,j+gi1,j1si
总方案数为 a l l A n s = ∑ i = 1 n g n , i allAns=\sum_{i=1}^ng_{n,i} allAns=i=1ngn,i
则最终答案 a n s = a l l A n s − s u b A n s ans=allAns-subAns ans=allAnssubAns

Code:

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int mod = 998244353;
const int N = 110;
const int M = 2010;

int n, m, f[N][N << 1], g[N][N];
int a[N][M], s[N];

signed main() {
	cin >> n >> m;
	for (int i = 1; i <= n; ++i) {
		for (int j = 1; j <= m; ++j) {
			cin >> a[i][j];
			s[i] += a[i][j];
			s[i] %= mod;
		}
	}

	int sans = 0;
	for (int c = 1; c <= m; c++) {
		f[0][n] = 1;
		for (int i = 1; i <= n; i++) {
			for (int j = n-i; j <= n+i; j++) {
				f[i][j] = f[i-1][j] + (j>0?f[i-1][j-1]:0) * a[i][c] + f[i-1][j+1] * (s[i] - a[i][c]);
				f[i][j] %= mod;
			}
		}
		for (int i = n+1; i <= n+n; i++) {
			sans += f[n][i];
			sans %= mod;
		}
	}

	g[0][0] = 1;
	for (int i = 1; i <= n; i++) {
		for (int j = 0; j <= n; j++) {
			g[i][j] = g[i - 1][j] + (j > 0 ? g[i - 1][j - 1] : 0) * s[i];
			g[i][j] %= mod;
		}
	}

	int ans = 0;
	for (int i = 1; i <= n; i++) {
		ans += g[n][i];
		ans %= mod;
	}

	ans -= sans;
	ans %= mod;
	cout << (ans + mod) % mod << endl;
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值