题目大意
「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 |
|
2 |
|
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 |
|
10 | freopen("input.txt", "r", stdin); |
11 | freopen("output.txt", "w", stdout); |
12 |
|
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 | } |