GESP认证C++编程真题解析 | 202603 六级
编程题
P15800 选数
【题目来源】
洛谷:[P15800 GESP202603 六级] 选数 - 洛谷
【题目描述】
给定两个包含 \(n\) 个整数的数组 \(a=[a_1,\dots,a_n]\) 与 \(b=[b_1,\dots,b_n]\)。你需要指定若干下标 \(p_1\lt \cdots\lt p_k\)(\(1\leq k\leq n\))使得以下条件成立:
- \(1\leq p_i\leq n\)(\(1\leq i\leq k\));
- \(p_{i+1}\geq p_i+b_{p_i}\)(\(1\leq i< k\))。
你需要在满足以上条件的前提下最大化 \(\sum_{i=1}^k a_{p_k}\),也即最大化数组 \(a\) 对应下标的整数之和。
【输入】
第一行,一个正整数 \(n\),表示数组长度。
第二行,\(n\) 个正整数 \(a_1,a_2,\dots,a_n\),表示数组 \(a\)。
第三行,\(n\) 个正整数 \(b_1,b_2,\dots,b_n\),表示数组 \(b\)。
【输出】
一行,一个整数,表示在满足下标条件的前提下,数组 \(a\) 对应下标的整数之和的最大值。
【输入样例】
4
1 2 3 4
3 3 1 1
【输出样例】
7
【算法标签】
《洛谷 P15800 选数》 #动态规划DP# #GESP# #2026#
【代码详解】
// 40分版本
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 100005;
int n, a[N], b[N], ans;
// dp[i] 表示以第 i 个活动结尾时,能获得的最大价值
int dp[N];
int len = 0;
signed 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++)
{
// 初始化:只选择第 i 个活动
dp[i] = a[i];
// 考虑在 i 之前的活动 j
for (int j = 1; j < i; j++)
{
// 检查活动 j 结束后是否可以安排活动 i
if (i >= j + b[j])
{
// 如果可以,则尝试从活动 j 转移到活动 i
dp[i] = max(dp[i], dp[j] + a[i]);
}
}
}
// 找到所有 dp[i] 中的最大值
for (int i = 1; i <= n; i++)
{
ans = max(ans, dp[i]);
}
cout << ans << endl;
return 0;
}
// 100分版本
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 100005;
int n, a[N], b[N];
// dp[i]: 到时间i为止能获得的最大收益(不包含在时间i开始的任务)
int dp[N];
signed main()
{
cin >> n;
for (int i = 1; i <= n; i++)
{
cin >> a[i];
}
for (int i = 1; i <= n; i++)
{
cin >> b[i];
}
int ans = 0;
for (int i = 1; i <= n; i++)
{
// 情况1: 在时间i开始执行任务i
// 收益 = 到时间i为止的最大收益 + 任务i的收益
ans = max(ans, dp[i] + a[i]);
// 如果执行任务i
int finish_time = i + b[i];
if (finish_time <= n)
{
// 在任务i结束时更新收益
dp[finish_time] = max(dp[finish_time], dp[i] + a[i]);
}
// 情况2: 在时间i不执行任何任务
// 收益延续到下一个时间点
dp[i + 1] = max(dp[i + 1], dp[i]);
}
// 注意:最终答案不一定是dp[n],因为可能在n时刻之前就已经得到了最大收益
// 我们已经在循环中通过ans记录了所有可能的最大值
cout << ans << endl;
return 0;
}
【运行结果】
4
1 2 3 4
3 3 1 1
7
P15801 完全二叉树
【题目来源】
洛谷:[P15801 GESP202603 六级] 完全二叉树 - 洛谷
【题目描述】
给定一棵包含 \(n\) 个结点的有根二叉树,结点依次以 \(1,2,\dots,n\) 编号,根结点编号为 \(1\)。
对于结点 \(i\),其左儿子的编号记为 \(l_i\),右儿子编号记为 \(r_i\)。特别地,如果左儿子不存在则 \(l_i=0\),如果右儿子不存在则 \(r_i=0\)。
树中每个结点都对应一棵以其为根的子树。请你求出给定有根树的所有 \(n\) 棵子树中,有多少棵子树是完全二叉树。
【输入】
第一行,一个正整数 \(n\),表示有根二叉树结点数量。
接下来 \(n\) 行,每行两个正整数 \(l_i,r_i\),表示结点 \(i\) 的左儿子编号和右儿子编号。
【输出】
输出一行,一个整数,表示所有子树中完全二叉树的数量。
【输入样例】
4
2 3
4 0
0 0
0 0
【输出样例】
4
【解题思路】

【算法标签】
《洛谷 P15801 完全二叉树》 #树形数据结构# #树形DP# #树的遍历# #GESP# #2026#
【代码详解】
#include <bits/stdc++.h>
using namespace std;
const int N = 100005;
int n, ans;
struct tree
{
int l, r, cnt, dep, flag;
} tr[N];
void dfs(int u)
{
int lchild = tr[u].l, rchild = tr[u].r;
// 如果是叶子节点
if (lchild == 0 && rchild == 0)
{
tr[u].cnt = 1; // 节点计数为1
tr[u].dep = 1; // 深度为1
tr[u].flag = 1; // 标记为完全二叉树
ans++; // 计数加1
return;
}
tr[u].cnt = 1; // 初始化计数,包含当前节点自身
// 遍历左子树
if (tr[u].l)
{
dfs(tr[u].l);
tr[u].cnt += tr[lchild].cnt; // 累加左子树的节点数
tr[u].dep = tr[lchild].dep + 1; // 计算深度
}
// 遍历右子树
if (tr[u].r)
{
dfs(tr[u].r);
tr[u].cnt += tr[rchild].cnt; // 累加右子树的节点数
tr[u].dep = max(tr[u].dep, tr[rchild].dep + 1); // 更新最大深度
}
// 处理只有一个子节点的情况
if (tr[lchild].dep == 1 && tr[rchild].dep == 0)
{
tr[rchild].flag = 1;
}
// 如果左右子树都是完全二叉树
if (tr[lchild].flag == 1 && tr[rchild].flag == 1)
{
// 情况1: 左右子树深度相等
if (tr[lchild].dep == tr[rchild].dep && tr[lchild].cnt == (1 << tr[lchild].dep) - 1)
{
tr[u].flag = 1; // 当前节点是完全二叉树的根
ans++;
}
// 情况2: 左子树深度比右子树大1
else if (tr[lchild].dep == tr[rchild].dep + 1 && tr[rchild].cnt == (1 << tr[rchild].dep) - 1)
{
tr[u].flag = 1; // 当前节点是完全二叉树的根
ans++;
}
}
}
int main()
{
cin >> n;
for (int i = 1; i <= n; i++)
{
int l, r;
cin >> l >> r;
tr[i].l = l, tr[i].r = r;
}
dfs(1);
cout << ans << endl;
return 0;
}
【运行结果】
4
2 3
4 0
0 0
0 0
4
浙公网安备 33010602011771号