题解:洛谷 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
【核心思想】
-
问题分析:给定两个数组 \(a\) 和 \(b\),需要选择若干下标 \(p_1 < p_2 < \cdots < p_k\),满足 \(p_{i+1} \geq p_i + b_{p_i}\)(即选择下标 \(i\) 后,至少间隔 \(b_i\) 个位置才能选下一个),最大化 \(\sum_{i=1}^k a_{p_i}\)。这是一个带间隔约束的最大子序列和问题,类似于加权活动选择问题。
-
算法选择:
- 线性DP:
dp[i]表示到时间 \(i\) 为止能获得的最大收益(不包含在时间 \(i\) 开始的任务) - 两种决策:在每个时间点 \(i\),可以选择执行活动 \(i\) 或不执行
- 时间轴DP:将问题转化为在时间轴上的决策,利用 \(dp[i]\) 记录到时间 \(i\) 的最优解
- 线性DP:
-
关键步骤:
- 初始化:
dp[i]初始为 0,表示到时间 \(i\) 的收益为 0 - 状态转移:遍历每个时间点 \(i\):
- 执行活动 \(i\):收益为
dp[i] + a[i],更新答案ans。若 \(i + b[i] \leq n\),则更新dp[i + b[i]] = max(dp[i + b[i]], dp[i] + a[i]) - 不执行活动:收益延续到下一个时间点,
dp[i + 1] = max(dp[i + 1], dp[i])
- 执行活动 \(i\):收益为
- 输出答案:
ans记录所有可能的最大值
- 初始化:
-
时间/空间复杂度:
- 时间复杂度:\(O(N)\),只需遍历一次时间轴
- 空间复杂度:\(O(N)\),DP数组大小
-
时间轴DP的核心思想:
- 状态定义技巧:
dp[i]表示到时间 \(i\) 为止的最大收益,但不包含在时间 \(i\) 开始的任务,避免重复计算 - 两种决策统一处理:在每个时间点同时考虑"执行"和"不执行"两种选择
- 延迟更新:执行任务 \(i\) 的收益在 \(i + b[i]\) 时刻才生效,体现间隔约束
- 答案实时维护:在遍历过程中实时更新
ans,避免最后遍历数组 - 适用于带时间/位置间隔约束的序列选择问题
- 状态定义技巧:
【解题思路】


【算法标签】
普及 #线性DP-一维
【代码详解】
// 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
浙公网安备 33010602011771号