GESP C++ 六级 DP 精选 4 题题解
本文档精选 4 道 GESP 六级真题。每道题包含:题目大意、状态定义、转移方程、边界条件、完整 AC 代码。
第 1 题:[GESP202506 六级] 学习小组
题目大意
有 n 名学生,编号 1~n 连续编号。将这些学生分成若干个连续编号的小组,每个小组人数不能超过 m,且每个小组有对应的奖励值。求分组后奖励值之和的最大值。
- 输入:第一行两个整数 n, m;第二行 n 个整数 a[i],a[i] 表示 i 个人组成的小组的奖励值。
思路分析
经典线性 DP(钢条切割模型)
- 状态定义:
dp[i]表示前 i 名学生分组的最大奖励值之和。 - 转移方程:枚举最后一个小组有 j 个人(1 ≤ j ≤ min(m, i)):
\[dp[i] = \max_{1 \le j \le \min(m,i)} \{ dp[i-j] + a[j] \}
\]
- 边界条件:
dp[0] = 0(0 个学生,奖励为 0)。 - 计算顺序:从小到大 i = 1 → n。
- 时间复杂度:O(nm)。
AC 代码
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 2005;
int n, m;
long long a[MAXN];
long long dp[MAXN];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
dp[0] = 0;
for (int i = 1; i <= n; i++) {
dp[i] = 0;
for (int j = 1; j <= min(m, i); j++) {
dp[i] = max(dp[i], dp[i - j] + a[j]);
}
}
cout << dp[n] << '\n';
return 0;
}
第 2 题:[GESP202603 六级] 选数
题目大意
有 n 个格子排成一排,每个格子 i 上有数字 a[i],并且每个格子有一个跳跃距离 b[i]。游戏规则:
- 初始站在第 1 格。
- 到达第 i 格时,可以选择:
- 拿取该格子的数字 a[i],然后向后跳跃 b[i] 格;
- 不拿,向后走 1 格。
- 走到第 n+1 格时游戏结束。
求能获得的最大数字之和。
思路分析
一维跳跃 DP(从后往前递推)
- 状态定义:
dp[i]表示到达第 i 格时,从第 i 格出发到游戏结束能获得的最大数字之和。 - 转移方程:
- 不拿:
dp[i] = dp[i + 1] - 拿:
dp[i] = a[i] + dp[i + b[i]](若 i+b[i] > n,则后面没有值)
- 不拿:
- 边界条件:
dp[n+1] = 0。 - 计算顺序:从后往前 i = n → 1。
AC 代码
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 100005;
int n;
int a[MAXN], b[MAXN];
long long dp[MAXN];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
for (int i = 1; i <= n; i++) {
cin >> b[i];
}
dp[n + 1] = 0;
for (int i = n; i >= 1; i--) {
// 不选,走一步
dp[i] = dp[i + 1];
// 选,跳 b[i] 步
if (i + b[i] <= n) {
dp[i] = max(dp[i], (long long)a[i] + dp[i + b[i]]);
} else {
dp[i] = max(dp[i], (long long)a[i]);
}
}
cout << dp[1] << '\n';
return 0;
}
第 3 题:[GESP202509 六级] 划分字符串
题目大意
给定一个由小写字母组成的字符串 s,长度为 n。要求将字符串划分成若干段,每一段中所有字符均不相同。每划分为一段长为 k 的子串,得分为 k²。求划分后得分之和的最大值。
思路分析
划分型 DP
- 状态定义:
dp[i]表示字符串前 i 个字符划分后的最大得分。 - 转移方程:枚举最后一段的起始位置 j,向前推直到遇到重复字符:
\[dp[i] = \max_{j}\{ dp[j] + (i-j)^2 \}
\]
- 边界条件:
dp[0] = 0。 - 技巧:用 vis 数组记录当前子串中出现过的字符,遇到重复就 break。
- 计算顺序:i = 1 → n。
AC 代码
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 2005;
int n;
string s;
long long dp[MAXN];
bool vis[26];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> s;
n = s.size();
s = " " + s; // 下标从 1 开始
dp[0] = 0;
for (int i = 1; i <= n; i++) {
memset(vis, 0, sizeof(vis));
dp[i] = 0;
// 向前枚举最后一段
for (int j = i - 1; j >= 0; j--) {
int c = s[j + 1] - 'a';
if (vis[c]) {
break; // 出现重复字符,停止扩展
}
vis[c] = true;
int len = i - j;
dp[i] = max(dp[i], dp[j] + (long long)len * len);
}
}
cout << dp[n] << '\n';
return 0;
}
第 4 题:[GESP202503 六级] 环线
题目大意
n 个格子排成一个环形,每个格子上有一个数(可能为负数)。请选择一段连续的格子(可以环绕一圈),使得选中的数字之和最大。求这个最大值。注意:至少选一个格子。
思路分析
环形最大子段和(拆环技巧)
分两种情况讨论:
- 情况 1:最大子段和不跨越首尾(普通线性)
→ 直接求普通最大子段和:cur_max = max(a[i], cur_max + a[i]) - 情况 2:最大子段和跨越首尾
→ 等价于:总和 - 普通最小子段和
→ans2 = sum - min_sub - 答案:
max(ans1, ans2)
⚠️ 特殊情况:如果所有数都是负数,必须取情况 1(选最大的负数)。
AC 代码
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 200005;
int n;
long long a[MAXN];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n;
long long sum = 0;
for (int i = 1; i <= n; i++) {
cin >> a[i];
sum += a[i];
}
// 最大子段和(情况 1)
long long max_sub = a[1], cur_max = a[1];
// 最小子段和(情况 2)
long long min_sub = a[1], cur_min = a[1];
long long max_val = a[1];
for (int i = 2; i <= n; i++) {
cur_max = max(a[i], cur_max + a[i]);
max_sub = max(max_sub, cur_max);
cur_min = min(a[i], cur_min + a[i]);
min_sub = min(min_sub, cur_min);
max_val = max(max_val, a[i]);
}
long long ans;
if (max_val < 0) {
// 所有数都是负数,选最大的一个
ans = max_val;
} else {
ans = max(max_sub, sum - min_sub);
}
cout << ans << '\n';
return 0;
}
三、DP 模型速查表
| 模型 | 核心状态 | 典型转移 | 代表题 |
|---|---|---|---|
| 线性 DP | dp[i] |
dp[i] = max(dp[i-j] + w[j]) |
学习小组 |
| 跳跃 DP | dp[i] |
dp[i] = a[i] + dp[i+b[i]] |
选数 |
| 划分 DP | dp[i] |
dp[i] = max(dp[j] + w(j+1,i)) |
划分字符串 |
| 环形 DP | 拆成两种情况 | max(不跨环, 总和-最小子段) |
环线 |

浙公网安备 33010602011771号