0%

2026牛客暑期多校 8——B-Deep Finesse(深算)

题目大意

深算

时间限制:C/C++/Rust/Pascal 2 秒,其他语言 4 秒
空间限制:C/C++/Rust/Pascal 1024 MB,其他语言 2048 MB

汐和风子正在一个庞大的数字世界里斗智:这个世界包含从 111010010^{100} 的所有整数。

对局开始前,汐要从这些整数中恰好挑选 nn互不相同的数作为自己的手牌,其余的所有数则全部归风子所有。

随后进行 nn 轮出牌。每一轮中:

  • 风子先手,从她自己剩余的数中打出一个;

  • 汐必须立刻从自己剩余的手牌中打出一个数作为回应。(她要出一个比风小的数)

危险的规则在于:若风子打出的数严格小于汐回应的数,风子当场获胜。只有当汐打出的数小于等于风子打出的数时,本轮才算安全通过,两个数随即被弃置,游戏继续进行。

汐要想取得胜利,必须撑过全部 nn 轮,始终不让风子打出更小的数。

更糟糕的是,游戏开始前风子还附加了一道限制:她指定了 mm 个特定的数,并宣布汐的初始手牌中必须包含这 mm 个数中的每一个

于是汐开始盘算:在所有包含这 mm 个指定数、大小为 nn 的手牌方案中,有多少种能够保证她必胜——即无论风子如何巧妙地出牌,汐都有办法撑到最后?

答案可能极其巨大,请对 998244353998244353 取模后输出。

每个测试点包含多组测试数据。

第一行包含一个整数 tt1t10001 \le t \le 1000),表示测试数据组数。

对于每组测试数据:

  • 第一行包含两个整数 nnmm1mn50001 \le m \le n \le 5000);

  • 第二行包含 mm 个整数 a1,a2,,ama_1, a_2, \ldots, a_m1ai1091 \le a_i \le 10^9),表示汐必须包含在手牌中的特定数字。保证 aa 中没有重复元素。

保证所有测试数据的 nn 之和不超过 50005000

对于每组测试数据,输出一个整数,表示能够保证汐必胜的初始手牌方案数,对 998244353998244353 取模。

1
2
3
4
5
6
7
3
1 1
2
2 1
2
6 3
1 4 5
1
2
3
0
1
34

第一组数据n=1n = 1,且手牌必须包含 22,因此汐的手牌被唯一确定为 {2}\lbrace 2 \rbrace。若风子打出 11,汐只能回应 22,此时 1<21 < 2,风子立即获胜。因此不存在必胜方案,答案为 00

第二组数据n=2n = 2,手牌必须包含 22。可以验证,只有手牌为 {1,2}\lbrace 1, 2 \rbrace 时汐才能保证获胜,因此答案为 11

第三组数据n=6n = 6,手牌必须包含 1,4,51, 4, 5 这三个数,另外还需从剩余整数中选出 33 个。在所有这样的手牌中,恰有 3434 种能保证汐必胜,因此答案为 3434

思路讲解

问:风子想最大化获胜概率,她会采取怎样的出牌策略?

答:风子为了尽可能让自己的牌小于汐的回应牌,一定会贪心地从小到大打出自己手中最小的 nn 张牌。因此,汐只需考虑如何战胜风子手中最小的 nn 个数即可。

问:为了抵挡风子打出的前 nn 小的牌,汐的手牌必须满足什么条件?

答:汐手牌中第 kk 小的牌必须小于等于风子手中第 kk 小的牌。这意味着,对于任意数字 xx,在从 11xx 的区间内,汐持有的牌的数量必须大于等于风子持有的牌的数量。

问:汐的手牌最大可以是多少?

答:由于汐要在前 2n2n 个数中获得比风子更多的牌(且两人最终各有 nn 张),汐的手牌必须全部包含在 112n2n 的范围内。如果有任何大于 2n2n 的数,汐在前 2n2n 中的牌数将小于 nn,必然在最后一轮失败。

问:如何将这个条件转化为经典的组合计数模型并求解?(这个确实是一个非常经典的 dp 模型啊)

答:将考察 11 2n2n 的过程视作折线图:属于汐的牌计为向右上走一步(+1),属于风子的牌计为向右下走一步(-1)。合法方案等价于从 (0,0)(0,0) 走到 (2n,0)(2n,0),中途不跌落 xx 轴以下,并且在指定的 mm 个位置必须向右上走。可以通过 O(n2)O(n^2) 动态规划求解。

问:为什么汐的必胜条件可以转化为卡特兰折线(括号序列)模型?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
#include <iostream>
#include <vector>

using namespace std;

const int MOD = 998244353;

void solve() {
int n;
int m;
cin >> n >> m;

vector<int> a(m);
bool possible = true;

for (int i = 0; i < m; ++i) {
cin >> a[i];
// 如果汐被强制要求选大于 2n 的牌,则她在前 2n 里的牌数不足 n 张,必然无法挡住风子前 n 小的牌
if (a[i] > 2 * n) {
possible = false;
}
}

if (!possible) {
cout << 0 << "\n";
return;
}

// 标记哪些数字是汐必须选的(即折线图中必须向右上走的位置)
vector<bool> is_fixed(2 * n + 1, false);
for (int i = 0; i < m; ++i) {
is_fixed[a[i]] = true;
}

// dp[j] 表示当前前缀和(相对高度)为 j 的方案数
vector<int> dp(n + 1, 0);
dp[0] = 1;

// next_dp 用于滚动数组更新
vector<int> next_dp(n + 1, 0);

for (int i = 1; i <= 2 * n; ++i) {
// 每轮开始前初始化 next_dp 为 0
for (int j = 0; j <= n; ++j) {
next_dp[j] = 0;
}

if (is_fixed[i]) {
// 当前数字必须属于汐,高度强制 +1
for (int j = 1; j <= n; ++j) {
next_dp[j] = dp[j - 1];
}
} else {
// 当前数字既可以给汐,也可以给风子
for (int j = 0; j <= n; ++j) {
// 如果分配给汐,高度 +1
if (j > 0) {
next_dp[j] = (next_dp[j] + dp[j - 1]) % MOD;
}
// 如果分配给风子,高度 -1,但前提是高度不能超过 n 且不跌破 0
if (j < n) {
next_dp[j] = (next_dp[j] + dp[j + 1]) % MOD;
}
}
}

// 将本轮计算的结果滚动覆盖回 dp 数组
for (int j = 0; j <= n; ++j) {
dp[j] = next_dp[j];
}
}

// 最终我们需要高度为 0,即汐和风子各拿到恰好 n 张牌
cout << dp[0] << "\n";
}

int main() {
// 优化输入输出流速度
ios_base::sync_with_stdio(false);
cin.tie(NULL);

int t;
if (cin >> t) {
while (t > 0) {
solve();
t--;
}
}

return 0;
}

AC代码

AC

https://ac.nowcoder.com/acm/contest/view-submission?submissionId=84449016

心路历程(WA,TLE,MLE……)