2022信奥赛C++提高组csp-s复赛真题及题解:策略游戏

2022信奥赛C++提高组csp-s复赛真题及题解:策略游戏

题目描述

小 L 和小 Q 在玩一个策略游戏。

有一个长度为 n n n 的数组 A A A 和一个长度为 m m m 的数组 B B B,在此基础上定义一个大小为 n × m n \times m n×m 的矩阵 C C C,满足 C i j = A i × B j C_{i j} = A_i \times B_j Cij=Ai×Bj。所有下标均从 1 1 1 开始。

游戏一共会进行 q q q 轮,在每一轮游戏中,会事先给出 4 4 4 个参数 l 1 , r 1 , l 2 , r 2 l_1, r_1, l_2, r_2 l1,r1,l2,r2,满足 1 ≤ l 1 ≤ r 1 ≤ n 1 \le l_1 \le r_1 \le n 1≤l1≤r1≤n、 1 ≤ l 2 ≤ r 2 ≤ m 1 \le l_2 \le r_2 \le m 1≤l2≤r2≤m。

游戏中,小 L 先选择一个 l 1 ∼ r 1 l_1 \sim r_1 l1∼r1 之间的下标 x x x,然后小 Q 选择一个 l 2 ∼ r 2 l_2 \sim r_2 l2∼r2 之间的下标 y y y。定义这一轮游戏中二人的得分是 C x y C_{x y} Cxy。

小 L 的目标是使得这个得分尽可能大,小 Q 的目标是使得这个得分尽可能小。同时两人都是足够聪明的玩家,每次都会采用最优的策略。

请问:按照二人的最优策略,每轮游戏的得分分别是多少?

输入格式

第一行输入三个正整数 n , m , q n, m, q n,m,q,分别表示数组 A A A,数组 B B B 的长度和游戏轮数。

第二行: n n n 个整数,表示 A i A_i Ai,分别表示数组 A A A 的元素。

第三行: m m m 个整数,表示 B i B_i Bi,分别表示数组 B B B 的元素。

接下来 q q q 行,每行四个正整数,表示这一次游戏的 l 1 , r 1 , l 2 , r 2 l_1, r_1, l_2, r_2 l1,r1,l2,r2。

输出格式

输出共 q q q 行,每行一个整数,分别表示每一轮游戏中,小 L 和小 Q 在最优策略下的得分。

输入输出样例 1
输入 1
复制代码
3 2 2
0 1 -2
-3 4
1 3 1 2
2 3 2 2
输出 1
复制代码
0
4
输入输出样例 2
输入 2
复制代码
6 4 5
3 -1 -2 1 2 0
1 2 -1 -3
1 6 1 4
1 5 1 4
1 4 1 2
2 6 3 4
2 5 2 3
输出 2
复制代码
0
-2
3
2
-1
说明/提示

【样例解释 #1】

这组数据中,矩阵 C C C 如下:

0 0 − 3 4 6 − 8 \] \\begin{bmatrix} 0 \& 0 \\\\ -3 \& 4 \\\\ 6 \& -8 \\end{bmatrix} 0−3604−8 在第一轮游戏中,无论小 L 选取的是 x = 2 x = 2 x=2 还是 x = 3 x = 3 x=3,小 Q 都有办法选择某个 y y y 使得最终的得分为负数。因此小 L 选择 x = 1 x = 1 x=1 是最优的,因为这样得分一定为 0 0 0。 而在第二轮游戏中,由于小 L 可以选 x = 2 x = 2 x=2,小 Q 只能选 y = 2 y = 2 y=2,如此得分为 4 4 4。 **【数据范围】** 对于所有数据, 1 ≤ n , m , q ≤ 10 5 1 \\le n, m, q \\le {10}\^5 1≤n,m,q≤105, − 10 9 ≤ A i , B i ≤ 10 9 -{10}\^9 \\le A_i, B_i \\le {10}\^9 −109≤Ai,Bi≤109。对于每轮游戏而言, 1 ≤ l 1 ≤ r 1 ≤ n 1 \\le l_1 \\le r_1 \\le n 1≤l1≤r1≤n, 1 ≤ l 2 ≤ r 2 ≤ m 1 \\le l_2 \\le r_2 \\le m 1≤l2≤r2≤m。 | 测试点编号 | n , m , q ≤ n, m, q \\le n,m,q≤ | 特殊条件 | |:-------------------------:|:-------------------------------:|:----:| | 1 1 1 | 200 200 200 | 1, 2 | | 2 2 2 | 200 200 200 | 1 | | 3 3 3 | 200 200 200 | 2 | | 4 ∼ 5 4 \\sim 5 4∼5 | 200 200 200 | 无 | | 6 6 6 | 1000 1000 1000 | 1, 2 | | 7 ∼ 8 7 \\sim 8 7∼8 | 1000 1000 1000 | 1 | | 9 ∼ 10 9 \\sim 10 9∼10 | 1000 1000 1000 | 2 | | 11 ∼ 12 11 \\sim 12 11∼12 | 1000 1000 1000 | 无 | | 13 13 13 | 10 5 {10}\^5 105 | 1, 2 | | 14 ∼ 15 14 \\sim 15 14∼15 | 10 5 {10}\^5 105 | 1 | | 16 ∼ 17 16 \\sim 17 16∼17 | 10 5 {10}\^5 105 | 2 | | 18 ∼ 20 18 \\sim 20 18∼20 | 10 5 {10}\^5 105 | 无 | 其中,特殊性质 1 为:保证 A i , B i \> 0 A_i, B_i \> 0 Ai,Bi\>0。 特殊性质 2 为:保证对于每轮游戏而言,要么 l 1 = r 1 l_1 = r_1 l1=r1,要么 l 2 = r 2 l_2 = r_2 l2=r2。 这是一道博弈论+区间查询的题目。我们需要理解两个玩家的策略,并高效处理多个区间查询。 ### 思路分析 ##### 1. 博弈策略分析 游戏规则: 1. 小L先选择 `x ∈ [l1, r1]` 2. 小Q后选择 `y ∈ [l2, r2]` 3. 得分 = `A[x] * B[y]` 4. 小L希望得分最大,小Q希望得分最小 这是一个零和博弈,我们可以倒推分析: * 对于小Q(后手):给定小L选择的 `x`,小Q会选择使 `A[x] * B[y]` 最小的 `y` * 如果 `A[x] ≥ 0`:小Q会选择 `B[y]` 最小的值 * 如果 `A[x] < 0`:小Q会选择 `B[y]` 最大的值 * 如果 `A[x] = 0`:无论小Q选什么,得分都是0 * 对于小L(先手):知道小Q的应对策略,小L会选择使最终得分最大的 `x` ##### 2. 数学表达式 对于给定的区间 `[l1, r1]` 和 `[l2, r2]`: 设: * `minB` = B在 `[l2, r2]` 中的最小值 * `maxB` = B在 `[l2, r2]` 中的最大值 * 对于A在 `[l1, r1]` 中的每个元素 `a`,小Q选择的 `B[y]` 为: B_choice(a) = { minB, if a ≥ 0 maxB, if a < 0 } * 最终得分为:`a * B_choice(a)` 小L的任务是选择 `a` 使得 `a * B_choice(a)` 最大。 ##### 3. 小L的最优选择策略 考虑A在 `[l1, r1]` 中的元素: 1. **非负数(a ≥ 0)** :得分为 `a * minB` * 如果 `minB ≥ 0`:得分非负,应选择最大的 `a`(即 `maxA_nonneg`) * 如果 `minB < 0`:得分为非正数,应选择最小的非负数(即 `minA_nonneg`),因为负得最少(或正得最多) 2. **负数(a \< 0)** :得分为 `a * maxB` * 如果 `maxB > 0`:得分为负数,应选择最大的负数(即 `maxA_neg`),因为负得最少 * 如果 `maxB ≤ 0`:得分为非负数,应选择最小的负数(即 `minA_neg`),因为正得最多 3. **零(a = 0)**:得分总是0 **小L的最终选择**:从以上候选值中选择得分最大的。 ##### 4. 数据结构设计 我们需要快速查询以下信息: * A区间:最大值、最小值、最大非负数、最小非负数、最大负数、最小负数、是否有零 * B区间:最小值、最大值 由于查询次数多(q ≤ 10\^5),需要O(log n)的查询: * **线段树**:每个节点维护上述信息 * **ST表**:也可以,但需要维护的信息较多 ##### 5. 算法步骤 对于每个查询 `(l1, r1, l2, r2)`: 1. 查询B区间:`minB`, `maxB` 2. 查询A区间:获取所有候选值 3. 根据 `minB` 和 `maxB` 的符号,计算候选得分 4. 选择最大得分 ### 代码实现 ```cpp #include using namespace std; typedef long long ll; const ll INF = 4e18; const int N = 1e5 + 5; int n, m, q; ll a[N], b[N]; // A数组的线段树节点 struct NodeA { ll mx, mn; // 最大值,最小值 ll mx_pos, mn_pos; // 最大非负数,最小非负数 ll mx_neg, mn_neg; // 最大负数,最小负数 bool has_zero; // 是否有0 }; // B数组的线段树节点(只需要最大值和最小值) struct NodeB { ll mx, mn; }; NodeA ta[N << 2]; NodeB tb[N << 2]; // 合并两个A节点 NodeA mergeA(const NodeA &l, const NodeA &r) { NodeA res; // 最大值和最小值 res.mx = max(l.mx, r.mx); res.mn = min(l.mn, r.mn); // 非负数 res.mx_pos = max(l.mx_pos, r.mx_pos); res.mn_pos = min(l.mn_pos, r.mn_pos); // 负数 res.mx_neg = max(l.mx_neg, r.mx_neg); res.mn_neg = min(l.mn_neg, r.mn_neg); // 是否有0 res.has_zero = l.has_zero || r.has_zero; return res; } // 合并两个B节点 NodeB mergeB(const NodeB &l, const NodeB &r) { return {max(l.mx, r.mx), min(l.mn, r.mn)}; } // 构建A线段树 void buildA(int p, int l, int r) { if (l == r) { ta[p].mx = ta[p].mn = a[l]; if (a[l] > 0) { ta[p].mx_pos = ta[p].mn_pos = a[l]; ta[p].mx_neg = -INF; // 用-INF表示不存在 ta[p].mn_neg = INF; // 用INF表示不存在 ta[p].has_zero = false; } else if (a[l] < 0) { ta[p].mx_pos = -INF; ta[p].mn_pos = INF; ta[p].mx_neg = ta[p].mn_neg = a[l]; ta[p].has_zero = false; } else { // a[l] == 0 ta[p].mx_pos = -INF; ta[p].mn_pos = INF; ta[p].mx_neg = -INF; ta[p].mn_neg = INF; ta[p].has_zero = true; } return; } int mid = (l + r) >> 1; buildA(p << 1, l, mid); buildA(p << 1 | 1, mid + 1, r); ta[p] = mergeA(ta[p << 1], ta[p << 1 | 1]); } // 构建B线段树 void buildB(int p, int l, int r) { if (l == r) { tb[p] = {b[l], b[l]}; return; } int mid = (l + r) >> 1; buildB(p << 1, l, mid); buildB(p << 1 | 1, mid + 1, r); tb[p] = mergeB(tb[p << 1], tb[p << 1 | 1]); } // 查询A区间 NodeA queryA(int p, int l, int r, int ql, int qr) { if (ql <= l && r <= qr) return ta[p]; int mid = (l + r) >> 1; if (qr <= mid) return queryA(p << 1, l, mid, ql, qr); if (ql > mid) return queryA(p << 1 | 1, mid + 1, r, ql, qr); return mergeA(queryA(p << 1, l, mid, ql, qr), queryA(p << 1 | 1, mid + 1, r, ql, qr)); } // 查询B区间 NodeB queryB(int p, int l, int r, int ql, int qr) { if (ql <= l && r <= qr) return tb[p]; int mid = (l + r) >> 1; if (qr <= mid) return queryB(p << 1, l, mid, ql, qr); if (ql > mid) return queryB(p << 1 | 1, mid + 1, r, ql, qr); return mergeB(queryB(p << 1, l, mid, ql, qr), queryB(p << 1 | 1, mid + 1, r, ql, qr)); } int main() { ios::sync_with_stdio(false); cin.tie(0); cin >> n >> m >> q; for (int i = 1; i <= n; i++) cin >> a[i]; for (int i = 1; i <= m; i++) cin >> b[i]; buildA(1, 1, n); buildB(1, 1, m); while (q--) { int l1, r1, l2, r2; cin >> l1 >> r1 >> l2 >> r2; // 查询B区间的最小值和最大值 NodeB nb = queryB(1, 1, m, l2, r2); ll minB = nb.mn, maxB = nb.mx; // 查询A区间的信息 NodeA na = queryA(1, 1, n, l1, r1); // 计算候选得分 ll ans = -INF; // 1. 考虑非负数的情况 if (na.mx_pos != -INF) { // 存在非负数 // 根据minB的符号选择 if (minB >= 0) { // 选择最大的非负数 ans = max(ans, na.mx_pos * minB); } else { // 选择最小的非负数 ans = max(ans, na.mn_pos * minB); } } // 2. 考虑负数的情况 if (na.mx_neg != -INF) { // 存在负数 // 根据maxB的符号选择 if (maxB > 0) { // 选择最大的负数(绝对值最小) ans = max(ans, na.mx_neg * maxB); } else { // 选择最小的负数(绝对值最大) ans = max(ans, na.mn_neg * maxB); } } // 3. 考虑0的情况 if (na.has_zero) { ans = max(ans, 0LL); } cout << ans << '\n'; } return 0; } ``` ### 功能分析 ##### 核心数据结构 1. **NodeA**:维护A区间的关键信息 * `mx`, `mn`:最大值和最小值 * `mx_pos`, `mn_pos`:最大非负数和最小非负数 * `mx_neg`, `mn_neg`:最大负数和最小负数 * `has_zero`:是否有零 2. **NodeB**:维护B区间的关键信息 * `mx`, `mn`:最大值和最小值 ##### 主要函数 1. **mergeA()**:合并两个A节点,用于线段树的区间合并 2. **mergeB()**:合并两个B节点 3. **buildA()** / **buildB()**:构建线段树 4. **queryA()** / **queryB()**:查询区间信息 ##### 算法流程 对于每个查询: 1. 查询B区间:获取 `minB` 和 `maxB` 2. 查询A区间:获取所有需要的极值信息 3. 根据博弈策略计算候选得分: * 非负数候选:根据 `minB` 符号选择最大或最小非负数 * 负数候选:根据 `maxB` 符号选择最大或最小负数 * 零候选:得分为0 4. 取所有候选得分的最大值 ##### 时间复杂度分析 * 建树:O(n + m) * 每次查询:O(log n + log m) * 总复杂度:O(q log(n+m)),满足1e5数据量要求 ##### 空间复杂度分析 * 线段树:O(4\*(n+m)) * 总空间:O(n+m) ##### 关键点总结 1. **博弈策略理解**:倒推分析,小Q总是选择使乘积最小的B值 2. **分类讨论**:根据A的符号和B的极值符号分情况处理 3. **数据结构优化**:使用线段树快速查询区间极值信息 4. **边界处理**:正确处理不存在的极值情况(用INF标记) *** ** * ** *** 专栏推荐:**信奥赛C++提高组csp-s初赛\&复赛真题题解(持续更新)** *** ** * ** *** > **各种学习资料,助力大家一站式学习和提升**!!! ```cpp #include using namespace std; int main(){ cout<<"########## 一站式掌握信奥赛知识! ##########"; cout<<"############# 冲刺信奥赛拿奖! #############"; cout<<"###### 课程购买后永久学习,不受限制! ######"; return 0; } ``` > ***1、csp信奥赛高频考点知识详解及案例实践:*** CSP信奥赛C++动态规划: [https://blog.csdn.net/weixin_66461496/category_13096895.html点击跳转](https://blog.csdn.net/weixin_66461496/category_13096895.html) CSP信奥赛C++标准模板库STL: [https://blog.csdn.net/weixin_66461496/category_13108077.html 点击跳转](https://blog.csdn.net/weixin_66461496/category_13108077.html) `信奥赛C++提高组csp-s知识详解及案例实践:` > ***2、csp信奥赛冲刺一等奖有效刷题题解:*** CSP信奥赛C++初赛及复赛高频考点真题解析(持续更新):[https://blog.csdn.net/weixin_66461496/category_12808781.html 点击跳转](https://blog.csdn.net/weixin_66461496/category_12808781.html) CSP信奥赛C++一等奖通关刷题题单及题解(持续更新):[https://blog.csdn.net/weixin_66461496/category_12673810.html 点击跳转](https://blog.csdn.net/weixin_66461496/category_12673810.html) > ***3、GESP C++考级真题题解:*** ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/f6d6cc3497984902ba9c62e1211dd3e8.png) GESP(C++ 一级+二级+三级)真题题解(持续更新):[https://blog.csdn.net/weixin_66461496/category_12858102.html 点击跳转](https://blog.csdn.net/weixin_66461496/category_12858102.html) ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/1d6808d861e94074a676203894381def.png) GESP(C++ 四级+五级+六级)真题题解(持续更新):[https://blog.csdn.net/weixin_66461496/category_12869848.html 点击跳转](https://blog.csdn.net/weixin_66461496/category_12869848.html) ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/35a50f179838441995a0cdb171f7beae.png) GESP(C++ 七级+八级)真题题解(持续更新): > ***4、CSP信奥赛C++竞赛拿奖视频课:*** [https://edu.csdn.net/course/detail/40437 点击跳转](https://edu.csdn.net/course/detail/40437) ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/60e257d0ff02448d800a6f884bfa4a59.png) ### · 文末祝福 · ```cpp #include using namespace std; int main(){ cout<<"跟着王老师一起学习信奥赛C++"; cout<<" 成就更好的自己! "; cout<<" csp信奥赛一等奖属于你! "; return 0; } ```

相关推荐
近津薪荼2 小时前
递归专题5——快速幂
c++·学习·算法
郝学胜-神的一滴2 小时前
跨平台通信的艺术与哲学:Qt与Linux Socket的深度对话
linux·服务器·开发语言·网络·c++·qt·软件构建
小龙报2 小时前
【数据结构与算法】指针美学与链表思维:单链表核心操作全实现与深度精讲
c语言·开发语言·数据结构·c++·物联网·算法·链表
一匹电信狗11 小时前
【LeetCode_547_990】并查集的应用——省份数量 + 等式方程的可满足性
c++·算法·leetcode·职场和发展·stl
Queenie_Charlie12 小时前
小陶的疑惑2
数据结构·c++·树状数组
Queenie_Charlie13 小时前
小陶与杠铃片
数据结构·c++·树状数组
CoderCodingNo13 小时前
【GESP】C++四级/五级练习题 luogu-P1223 排队接水
开发语言·c++·算法
sycmancia14 小时前
C++进阶01——示例
开发语言·c++
CoderCodingNo14 小时前
【GESP】C++五级/四级练习题 luogu-P1413 坚果保龄球
开发语言·c++·算法