只要你跑的够快,锅就追不上你

0%

「TJOI 2019」唱、跳、rap 和篮球(容斥原理)

题目大意

「TJOI 2019」唱、跳、rap 和篮球(Luogu 5339)

共有 $a + b + c + d$ 个人,有 $a$ 个人喜欢唱,有 $b$ 个人喜欢跳,有 $c$ 个人喜欢 rap,有 $d$ 个人喜欢篮球。要求选出 $n$ 个人排队,使得不存在一个位置 $i$,满足位置 $i, i + 1, i + 2, i + 3$ 上的人同时喜欢唱、跳、rap 和篮球。我们称两种方案不同当且仅当有一个位置上的人的爱好不同,求共有多少不同的方案 $\bmod 998244353$ 的结果。

数据范围:$n \le 10^3, a, b, c, d \le 500$。

思路分析

网上好多题解都是 $O(n^2 \log n)$ 的,这里讲一个 $O(n^2)$ 的做法。

我们记唱为 C,跳为 T,rap 为 R,篮球为 L,位置 $i, i + 1, i + 2, i + 3$ 上的人同时喜欢唱、跳、rap 和篮球的组叫坤坤组。考虑枚举坤坤组的数量 $k$,然后除了 $k$ 个坤坤组的位置随便选,最后的答案就是 $\sum_{k} (-1)^k \times \sum_{\text{queue}} [\text{cnt_kun(queue)} = k]$。

对于某个 $k$,我们先要选出 $k$ 个坤坤组的位置。考虑 $k$ 个坤坤组形成的位置集合 ${ p_1, p_2, \cdots, p_k }$,我们将 $p_i \leftarrow p_i - 3(i - 1)$ 后,限制就变成了 $1 \le p_1 < p_2 < \cdots < p_k \le n - 3k$,所以方案数就是 $\binom{n - 3k}{k}$。

接下来我们考虑剩下的位置。此时,假设我们还剩 $C = a - k$ 个 C,$T = b - k$ 个 T,$R = c - k$ 个 R,$L = d - k$ 个 L,要把他们放到 $m = n - 4k$ 个位置中。我们可以将其看作是一个多项式 $A = (x + y + z + w)^m$,将计算结果中 $x$ 次数不超过 $C$,$y$ 次数不超过 $T$,$z$ 次数不超过 $R$,$w$ 次数不超过 $L$,的所有项的系数相加就可以得到答案。我们将其展开,得到答案为:

对于外层的求和可以枚举,对于内层的求和,发现一定可以写成 $\sum_{i = l}^{r} \binom{x}{i}$ 的形式。预处理组合数前缀和即可,时间复杂度 $O(n^2)$。

代码实现

一个坑点是 $k$ 必须循环到 $\min {\displaystyle \frac{n}{4}, a, b, c, d}$,如果再大会出错。

1
#include <bits/stdc++.h>
2
#define debug(...) fprintf(stderr, __VA_ARGS__)
3
using namespace std;
4
5
const int maxn = 1e3, mod = 998244353;
6
int n, a, b, c, d, lim, ans, num[maxn + 3][maxn + 3], sum[maxn + 3][maxn + 3], f[maxn + 3], g[maxn + 3];
7
8
void settings() {
9
#ifndef ONLINE_JUDGE
10
	freopen("input.txt", "r", stdin);
11
	freopen("output.txt", "w", stdout);
12
#endif
13
}
14
15
void prework(int n) {
16
	lim = min(n / 4, min(min(a, b), min(c, d)));
17
	for (int i = 0; i <= n; i++) {
18
		num[i][0] = num[i][i] = 1;
19
		for (int j = 1; j < i; j++) {
20
			num[i][j] = num[i - 1][j - 1] + num[i - 1][j];
21
			num[i][j] < mod ? 0 : num[i][j] -= mod;
22
		}
23
	}
24
	for (int i = 0; i <= n; i++) {
25
		sum[i][0] = num[i][0];
26
		for (int j = 1; j <= i; j++) {
27
			sum[i][j] = sum[i][j - 1] + num[i][j];
28
			sum[i][j] < mod ? 0 : sum[i][j] -= mod;
29
		}
30
	}
31
}
32
33
int func(int i, int l, int r) {
34
	if (l > r) return 0;
35
	l < 0 ? l = 0 : 0;
36
	r > i ? r = i : 0;
37
	int t = sum[i][r] - (l ? sum[i][l - 1] : 0);
38
	return t < 0 ? t + mod : t;
39
}
40
41
int main() {
42
	settings();
43
	scanf("%d %d %d %d %d", &n, &a, &b, &c, &d);
44
	prework(n);
45
	for (int i = 0; i <= lim; i++) {
46
		int tot = n - i * 4, cnt = num[n - i * 3][i], res = 0;
47
		int C = a - i, T = b - i, R = c - i, L = d - i; // Chang, Tiao, Rap, Lanqiu
48
		for (int j = 0, CT, RL; j <= tot; j++) {
49
			CT = j, RL = tot - j;
50
			res = (res + 1ll * func(CT, CT - T, C) * func(RL, RL - L, R) % mod * num[tot][j]) % mod;
51
		}
52
		ans = (ans + 1ll * (i & 1 ? mod - 1 : 1) * cnt % mod * res) % mod;
53
	}
54
	printf("%d\n", ans);
55
	return 0;
56
}