CSPS2022补题--策略游戏
策略游戏
对于这种题目我感觉会比较难-》主要是我经常会陷入死胡同, 然后触发无限递归, 然后推式子能力不足
首先理解题意:我们有两个数组, 然后有两个人, 他们的策略是已经定好的,然后给定两个区间, 也就是两个人可以选定的范围, 接着两个人都有自己的目标--一个是让答案最大化, 一个是最小化, 然后这个答案是把他们两个人选的给乘起来, 问每一次的得分是多少
首先我先说下我做这道题目的时候陷入的问题泥潭吧, 首先就是你第一眼看到这个题目, 你会想到这是不是一个博弈论, 然后你会想到NIM游戏啊, 巴什博弈啊这种东西, 然后很显然会想到去打个表看一下啊, 推个SG函数看下, 这种东西, 然后这道题就很难做了
当然, 我们其实有一个\(O(q \times n \times m)\)的做法, 然后我们可以通过一些数据来看下我们会在什么时候选择哪一些数作为最优答案。
Qwen's think
你的代码整体思路
这道题可以看成:
- 小 L 先选一个下标
x ∈ [l1, r1] - 小 Q 再选一个下标
y ∈ [l2, r2] - 得分是
A[x] * B[y] - 小 L 想让得分尽量大,小 Q 想让得分尽量小
因为小 Q 是在小 L 选完 x 之后再选 y,所以对于小 L 选定的某个 A[x],小 Q 一定会选择让乘积最小的 B[y]。
所以小 L 的真实目标不是直接最大化 A[x] * B[y],而是:
max over x in [l1,r1] of ( min over y in [l2,r2] of A[x] * B[y] )
你的代码用 ST 表 预处理区间最值,然后对每个询问快速得到候选答案。
为什么不能只取 A 的最大值或最小值?
因为乘积的正负会影响最优选择。
例如:
A[x] = -5
B[y] 可能是 -3 或 4
那么:
-5 * -3 = 15
-5 * 4 = -20
小 Q 想让结果尽量小,所以会选 4,结果是 -20。
再看:
A[x] = 5
B[y] 可能是 -3 或 4
那么:
5 * -3 = -15
5 * 4 = 20
小 Q 会选 -3,结果是 -15。
所以小 L 选 A[x] 时,不能只看 A[x] 最大或最小,还要看它和 B 区间最小值、最大值相乘后,小 Q 会把结果压到多少。
小 Q 对固定 A[x] 会怎么选?
对于固定的 A[x],小 Q 要在 [l2, r2] 中选 B[y],使 A[x] * B[y] 最小。
分三种情况:
A[x] 的符号 |
小 Q 的最优选择 | 最小乘积 |
|---|---|---|
A[x] > 0 |
选最小 B[y] |
A[x] * bmin |
A[x] = 0 |
选任意 B[y] |
0 |
A[x] < 0 |
选最大 B[y] |
A[x] * bmax |
所以你的代码里这一段:
amax * (amax >= 0 ? bmin : bmax)
表示:如果小 L 选 A[x] = amax,那么小 Q 会把结果压成:
amax >= 0 ? amax * bmin : amax * bmax
同理:
amin * (amin >= 0 ? bmin : bmax)
表示小 L 选 A[x] = amin 时,小 Q 压到的结果。
为什么还要维护负数最大值和非负数最小值?
因为 A[x] 的符号很关键。
在同一个区间里:
- 最大
A[x]可能是正数 - 最小
A[x]可能是负数 - 但真正影响答案的,可能不是全局最大或最小,而是:
- 负数里最大的那个,也就是最接近
0的负数 - 非负数里最小的那个,也就是最接近
0的非负数
- 负数里最大的那个,也就是最接近
所以你的代码维护了:
a_fmax:区间内负数的最大值
a_zmin:区间内非负数的最小值
初始化:
a_fmax[i][0] = (x < 0) ? x : MIN_INF;
a_zmin[i][0] = (x >= 0) ? x : MAX_INF;
意思是:
- 如果
A[i] < 0,它可能是负数最大值候选 - 如果
A[i] >= 0,它可能是非负数最小值候选 - 否则用无效值占位
合并时:
a_fmax[j][i] = max(a_fmax[j][i-1], a_fmax[mid][i-1]);
a_zmin[j][i] = min(a_zmin[j][i-1], a_zmin[mid][i-1]);
这是标准的 ST 表合并方式。
为什么候选答案只需要看这几个 A[x]?
你的代码最终用这几个候选值更新答案:
amax
amin
a_fmax
a_zmin
原因是:
对于每个 A[x],小 Q 会把它压成:
A[x] >= 0 ? A[x] * bmin : A[x] * bmax
这个函数在正数区间和负数区间内都是单调的:
- 如果
A[x] >= 0,那么A[x] * bmin随A[x]增大而单调变化 - 如果
A[x] < 0,那么A[x] * bmax随A[x]增大而单调变化
所以在一个区间里,真正可能成为最优的 A[x],通常出现在这些边界位置:
- 最大
A[x] - 最小
A[x] - 负数里最大
A[x] - 非负数里最小
A[x]
因此你不需要枚举整个区间,只需要用 ST 表查询这些极值。
ST 表部分讲解
你的代码先预处理 st[i]:
st[1] = 0;
for(int i = 2; i <= max(n,m); i++){
st[i] = st[i/2] + 1;
}
这里 st[len] 表示:
floor(log2(len))
也就是区间长度 len 对应的最大幂次。
然后对 A 建 ST 表:
for(int i = 1; i < Log; i++){
for(int j = 1; j + (1<<i) - 1 <= n; j++){
int mid = j + (1<<(i-1));
a_max[j][i] = max(a_max[j][i-1], a_max[mid][i-1]);
a_min[j][i] = min(a_min[j][i-1], a_min[mid][i-1]);
a_fmax[j][i] = max(a_fmax[j][i-1], a_fmax[mid][i-1]);
a_zmin[j][i] = min(a_zmin[j][i-1], a_zmin[mid][i-1]);
}
}
这是 ST 表的标准构建:
区间 [j, j + 2^i - 1]
= 左半段 [j, j + 2^(i-1) - 1]
+ 右半段 [j + 2^(i-1), j + 2^i - 1]
对 B 也类似:
for(int i = 1; i < Log; i++){
for(int j = 1; j + (1<<i) - 1 <= m; j++){
int mid = j + (1<<(i-1));
b_max[j][i] = max(b_max[j][i-1], b_max[mid][i-1]);
b_min[j][i] = min(b_min[j][i-1], b_min[mid][i-1]);
}
}
查询部分讲解
对于每个询问:
cin >> l1 >> r1 >> l2 >> r2;
先查询 A[l1..r1] 的四个极值。
区间长度:
int len_a = r1 - l1 + 1;
int k_a = st[len_a];
int split_a = r1 - (1<<k_a) + 1;
然后用两段覆盖查询:
int amax = max(a_max[l1][k_a], a_max[split_a][k_a]);
int amin = min(a_min[l1][k_a], a_min[split_a][k_a]);
int afmax = max(a_fmax[l1][k_a], a_fmax[split_a][k_a]);
int azmin = min(a_zmin[l1][k_a], a_zmin[split_a][k_a]);
这是 ST 表区间查询的常见写法:
[l1, r1]
= [l1, l1 + 2^k - 1] 和 [r1 - 2^k + 1, r1]
这两段一定覆盖整个区间。
对 B[l2..r2] 也类似:
int len_b = r2 - l2 + 1;
int k_b = st[len_b];
int split_b = r2 - (1<<k_b) + 1;
int bmax = max(b_max[l2][k_b], b_max[split_b][k_b]);
int bmin = min(b_min[l2][k_b], b_min[split_b][k_b]);
答案合并部分讲解
int res = MIN_INF;
res = max(res, amax * (amax >= 0 ? bmin : bmax));
res = max(res, amin * (amin >= 0 ? bmin : bmax));
if(afmax != MIN_INF){
res = max(res, afmax * (afmax >= 0 ? bmin : bmax));
}
if(azmin != MAX_INF){
res = max(res, azmin * (azmin >= 0 ? bmin : bmax));
}
这里的含义是:
- 小 L 可能选
amax - 小 L 可能选
amin - 小 L 可能选负数最大值
afmax - 小 L 可能选非负数最小值
azmin
对每个候选 A[x],小 Q 会把它压成:
A[x] >= 0 ? A[x] * bmin : A[x] * bmax
然后小 L 取这些候选中的最大值。
一个需要注意的地方
你的代码里:
res = max(res, amax * (amax >= 0 ? bmin : bmax));
res = max(res, amin * (amin >= 0 ? bmin : bmax));
这两行即使 amax 或 amin 不存在正负数意义,也不会出错,因为 amax 和 amin 一定是区间里的真实值。
但 afmax 和 azmin 可能不存在:
- 如果区间里没有负数,
afmax == MIN_INF - 如果区间里没有非负数,
azmin == MAX_INF
所以后面要判断:
if(afmax != MIN_INF)
if(azmin != MAX_INF)
否则会把无效值参与乘法,导致错误。
复杂度分析
预处理:
O(n log n + m log m)
每次询问:
O(1)
总复杂度:
O(n log n + m log m + q)
空间复杂度大约是:
O(n log n + m log m)
对于 n, m, q <= 1e5,这个做法可以通过。
总结
你的代码核心逻辑是:
- 小 Q 会在小 L 选完
A[x]后,选择使乘积最小的B[y] - 对于固定
A[x],小 Q 的选择取决于A[x]的正负:A[x] >= 0:选bminA[x] < 0:选bmax
- 小 L 要在
[l1, r1]中选一个A[x],使得小 Q 压完后的结果最大 - 由于乘积符号影响单调性,只需要考虑:
A区间最大值A区间最小值A区间负数最大值A区间非负数最小值
- 用 ST 表维护这些极值,实现
O(1)查询
整体思路是正确的,关键点是:不能只看 A 的最大值或最小值,必须按符号分类讨论乘积的单调性。
需要我帮你整理一份这道题的模板化代码吗?方便你以后直接套用。

浙公网安备 33010602011771号