2023年12月GESP真题及题解(C++七级): 纸牌游戏

题目描述
你和小杨在玩一个纸牌游戏。
你和小杨各有 3 3 3 张牌,分别是 0 、 1 、 2 0、1、2 0、1、2。你们要进行 N N N 轮游戏,每轮游戏双方都要出一张牌,并按 1 1 1 战胜 0 0 0, 2 2 2 战胜 1 1 1, 0 0 0 战胜 2 2 2 的规则决出胜负。第 i i i 轮的胜者可以获得 2 × a i 2 \times a_i 2×ai 分,败者不得分,如果双方出牌相同,则算平局,二人都可获得 a i a_i ai 分 ( i = 1 , 2 , ⋯ , N ) (i=1,2,\cdots,N) (i=1,2,⋯,N)。
玩了一会后,你们觉得这样太过于单调,于是双方给自己制定了不同的新规则。小杨会在整局游戏开始前确定自己全部 n n n 轮的出牌,并将他的全部计划告诉你;而你从第 2 2 2 轮开始,要么继续出上一轮出的牌,要么记一次"换牌"。游戏结束时,你换了 t t t 次牌,就要额外扣 b 1 + ⋯ + b t b_1+\cdots+b_t b1+⋯+bt 分。
请计算出你最多能获得多少分。
输入格式
第一行一个整数 N N N,表示游戏轮数。
第二行 N N N 个用单个空格隔开的非负整数 a 1 , ⋯ , a N a_1,\cdots,a_N a1,⋯,aN,意义见题目描述。
第三行 N − 1 N-1 N−1 个用单个空格隔开的非负整数 b 1 , ⋯ , b N − 1 b_1,\cdots,b_{N-1} b1,⋯,bN−1,表示换牌的罚分,具体含义见题目描述。由于游戏进行 N 轮,所以你至多可以换 N − 1 N-1 N−1 次牌。
第四行 N N N 个用单个空格隔开的整数 c 1 , ⋯ , c N c_1,\cdots,c_N c1,⋯,cN,依次表示小杨从第 1 1 1 轮至第 N N N 轮出的牌。保证 c i ∈ 0 , 1 , 2 c _i\in{0,1,2} ci∈0,1,2。
输出格式
一行一个整数,表示你最多获得的分数。
输入输出样例 1
输入 1
4
1 2 10 100
1 100 1
1 1 2 0
输出 1
219
输入输出样例 2
输入 2
6
3 7 2 8 9 4
1 3 9 27 81
0 1 2 1 2 0
输出 2
56
说明/提示
样例解释 1
你可以第 1 1 1 轮出 0 0 0,并在第 2 , 3 2,3 2,3 轮保持不变,如此输掉第 1 , 2 1,2 1,2 轮,但在第 3 3 3 轮中取胜,获得 2 × 10 = 20 2×10=20 2×10=20 分;
随后,你可以在第 4 4 4 轮中以扣 1 1 1 分为代价改出 1 1 1 ,并在第 4 4 4 轮中取得胜利,获得 2 × 100 = 200 2×100=200 2×100=200 分。
如此,你可以获得最高的总分 20 + 200 − 1 = 219 20+200-1=219 20+200−1=219。
数据范围
对于 30 % 30\% 30% 的测试点,保证 N ≤ 15 N\le15 N≤15。
对于 60 % 60\% 60% 的测试点,保证 N ≤ 100 N\le100 N≤100。
对于所有测试点,保证 N ≤ 1 , 000 N \le 1,000 N≤1,000;保证 0 ≤ a i , b i ≤ 10 6 0 \le a_i,b_i \le 10^6 0≤ai,bi≤106。
思路分析
这是一个动态规划问题。我们需要在已知对手所有出牌的情况下,选择自己的出牌序列,使得总得分最大化,同时考虑换牌的惩罚成本。
核心思路
-
状态定义:
f[i][k][s]表示进行了前i轮游戏,换了k次牌,且第i轮出牌为s(0,1,2) 时的最大得分。
-
状态转移:
- 如果第
i轮出牌与第i-1轮相同:f[i][k][s] = max(f[i][k][s], f[i-1][k][s] + getScore(i, s)) - 如果第
i轮出牌与第i-1轮不同:f[i][k][s] = max(f[i][k][s], f[i-1][k-1][t] + getScore(i, s) - b[k])
其中t是上一轮的出牌,b[k]是第k次换牌的惩罚分
- 如果第
-
得分计算:
- 根据题目规则计算每轮得分:平局得
a[i],胜利得2*a[i],失败得0
- 根据题目规则计算每轮得分:平局得
-
初始化:
- 第一轮没有换牌,直接计算每种出牌的得分
-
答案:
- 所有
f[N][k][s]中的最大值
- 所有
时间复杂度
- 状态数:
O(N * N * 3) = O(3N²) - 转移:每个状态最多从 3 个状态转移过来
- 总复杂度:
O(9N²),对于N ≤ 1000可以接受
代码实现
cpp
#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
const long long INF = 1e18;
int n;
int a[N], b[N], c[N];
long long f[N][N][3]; // f[i][k][s]: 前i轮,换k次牌,第i轮出牌为s的最大得分
// 计算第i轮出牌s的得分(不考虑换牌惩罚)
int getScore(int i, int s) {
if (s == c[i]) return a[i]; // 平局
if ((s == 1 && c[i] == 0) || (s == 2 && c[i] == 1) || (s == 0 && c[i] == 2)) {
return 2 * a[i]; // 胜利
}
return 0; // 失败
}
int main() {
// 输入
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = 1; i < n; i++) cin >> b[i];
for (int i = 1; i <= n; i++) cin >> c[i];
// 初始化
for (int i = 0; i <= n; i++) {
for (int k = 0; k <= n; k++) {
for (int s = 0; s < 3; s++) {
f[i][k][s] = -INF;
}
}
}
// 第一轮:没有换牌,直接计算得分
for (int s = 0; s < 3; s++) {
f[1][0][s] = getScore(1, s);
}
// 动态规划
for (int i = 2; i <= n; i++) {
for (int k = 0; k < i; k++) { // 最多换i-1次牌
for (int s = 0; s < 3; s++) { // 当前出牌
// 情况1:不出牌不变(不换牌)
if (f[i-1][k][s] != -INF) {
f[i][k][s] = max(f[i][k][s], f[i-1][k][s] + getScore(i, s));
}
// 情况2:出牌改变(换牌),需要k>0
if (k > 0) {
for (int t = 0; t < 3; t++) { // 上一轮出牌
if (t != s && f[i-1][k-1][t] != -INF) {
f[i][k][s] = max(f[i][k][s], f[i-1][k-1][t] + getScore(i, s) - b[k]);
}
}
}
}
}
}
// 输出答案:所有可能状态的最大值
long long ans = -INF;
for (int k = 0; k < n; k++) {
for (int s = 0; s < 3; s++) {
ans = max(ans, f[n][k][s]);
}
}
cout << ans << endl;
return 0;
}
功能分析
1. 数据存储
a[i]: 第i轮的基础得分b[i]: 第i次换牌的惩罚分c[i]: 对手在第i轮的出牌f[i][k][s]: DP 状态数组
2. 状态设计
- 三维 DP:轮数 × 换牌次数 × 当前出牌
- 使用
-INF初始化表示不可达状态
3. 得分计算函数
- 根据游戏规则(剪刀石头布变种)计算得分
- 平局:
a[i]分 - 胜利:
2*a[i]分 - 失败:0 分
4. 转移方程实现
- 不换牌:直接从相同出牌状态转移,不扣分
- 换牌 :从不同出牌状态转移,扣除
b[k]分 - 注意换牌次数
k的限制
5. 边界处理
- 第一轮特殊处理:没有上一轮,不涉及换牌
- 换牌次数不超过轮数减一
- 使用
-INF避免非法状态转移
完整GESP C++考级真题题解专栏:

GESP(C++ 一级+二级+三级)真题题解(持续更新):https://blog.csdn.net/weixin_66461496/category_12858102.html 点击跳转

GESP(C++ 四级+五级+六级)真题题解(持续更新):https://blog.csdn.net/weixin_66461496/category_12869848.html 点击跳转

GESP(C++ 七级+八级)真题题解(持续更新):
https://blog.csdn.net/weixin_66461496/category_13117178.html
更多csp信奥赛C++学习资料汇总:
1、csp/信奥赛C++,完整信奥赛系列课程(永久学习):
https://edu.csdn.net/lecturer/7901 点击跳转



2、CSP信奥赛C++竞赛拿奖视频课:
https://edu.csdn.net/course/detail/40437 点击跳转

3、csp信奥赛高频考点知识详解及案例实践:
CSP信奥赛C++动态规划:
https://blog.csdn.net/weixin_66461496/category_13096895.html点击跳转
CSP信奥赛C++标准模板库STL:
https://blog.csdn.net/weixin_66461496/category_13108077.html 点击跳转
信奥赛C++提高组csp-s知识详解及案例实践:
https://blog.csdn.net/weixin_66461496/category_13113932.html
4、csp信奥赛冲刺一等奖有效刷题题解:
CSP信奥赛C++初赛及复赛高频考点真题解析(持续更新):https://blog.csdn.net/weixin_66461496/category_12808781.html 点击跳转
CSP信奥赛C++一等奖通关刷题题单及题解(持续更新):https://blog.csdn.net/weixin_66461496/category_12673810.html 点击跳转
· 文末祝福 ·
cpp
#include<bits/stdc++.h>
using namespace std;
int main(){
cout<<"跟着王老师一起学习信奥赛C++";
cout<<" 成就更好的自己! ";
cout<<" csp信奥赛一等奖属于你! ";
return 0;
}