题解 | #星际勘探者#
星际勘探者
https://www.nowcoder.com/practice/1a9c4a33372443a9944cd458a3269dac
题目链接
题目描述
模拟一艘星际勘探舰穿越由 m 颗小行星组成的直线路径。舰船从一个初始能量 n 开始,每航行到下一颗小行星固定消耗能量 1。在整个任务中,最多可以使用采集器 k 次,采集第 i 颗小行星可以补充能量 a[i]。目标是计算在整个任务过程中的任意时刻,舰船所能达到的最大能量值。
- 约束条件:
- 只能沿小行星编号递增的方向前进。
- 航行前能量必须大于 0。
- 每颗小行星最多采集一次。
解题思路
这是一个典型的动态规划问题,可以看作是 0/1 背包问题的一个变种。我们需要在遍历小行星的过程中,对每个小行星做决策(采集或不采集),并维护在不同采集次数下的最大能量状态。
-
DP 状态定义
- 我们可以使用一维 DP 数组来优化空间复杂度。
- 定义
dp[j]为:在处理完当前考察的小行星后,总共使用了j次采集器所能达到的最大剩余能量。数组大小为k+1。
-
DP 状态转移
- 我们按顺序遍历每一颗小行星
a[i]。 - 对于每颗小行星,我们需要根据上一个阶段的
dp状态,计算出当前阶段的新dp状态。为避免状态更新的相互干扰,我们可以使用一个临时数组temp_dp来存储新状态。 - 遍历所有可能的采集次数
j(从0到k):temp_dp[j]的值由两种可能的前置状态共同决定:- 路径一:不采集当前小行星
a[i]。- 这个决策必须基于上一个阶段已经采集了
j次的状态,即dp[j]。 - 前提条件是
dp[j] > 0(有足够能量航行)。 - 如果满足,则航行后能量为
dp[j] - 1。
- 这个决策必须基于上一个阶段已经采集了
- 路径二:采集当前小行星
a[i]。- 这个决策必须基于上一个阶段已经采集了
j-1次的状态,即dp[j-1]。 - 前提条件是
j > 0(有采集次数) 且dp[j-1] > 0(有足够能量航行)。 - 如果满足,则航行并采集后能量为
dp[j-1] - 1 + a[i]。
- 这个决策必须基于上一个阶段已经采集了
- 路径一:不采集当前小行星
- 新的
temp_dp[j]就是这两条路径所能达到的能量的最大值。如果两条路径都不可行,则该状态不可达(值为-1)。
- 在计算完当前小行星的所有新状态后,将
temp_dp数组的内容复制回dp数组,为下一轮迭代做准备。
- 我们按顺序遍历每一颗小行星
-
追踪最大能量
- 题目要求的是过程中的最大能量,而不仅仅是最终能量。
- 因此,需要一个全局变量
max_overall_energy,初始化为n。 - 在每次计算出一个有效的
temp_dp[j]值后,都需要用它来更新max_overall_energy。
-
初始化
dp数组大小为k+1。dp[0] = n:表示初始状态,0 次采集,能量为n。dp[1...k]初始化为无效值(如 -1),表示这些状态尚未达到。
代码
#include <iostream>
#include <vector>
#include <numeric>
#include <algorithm>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(NULL);
int m, n, k;
cin >> m >> n >> k;
vector<int> a(m);
for (int i = 0; i < m; ++i) {
cin >> a[i];
}
vector<long long> dp(k + 1, -1);
dp[0] = n;
long long max_overall_energy = n;
for (int i = 0; i < m; ++i) { // 遍历每颗小行星 a[i]
vector<long long> temp_dp = dp; // 使用临时数组防止状态干扰
for (int j = 0; j <= k; ++j) {
// 来源1: 不采集小行星 a[i]。
// 从上一步的 dp[j] 状态转移而来。
long long energy_from_no_collect = -1;
if (dp[j] > 0) {
energy_from_no_collect = dp[j] - 1;
}
// 来源2: 采集小行星 a[i]。
// 从上一步的 dp[j-1] 状态转移而来。
long long energy_from_collect = -1;
if (j > 0 && dp[j - 1] > 0) {
energy_from_collect = dp[j - 1] - 1 + a[i];
}
temp_dp[j] = max(energy_from_no_collect, energy_from_collect);
max_overall_energy = max(max_overall_energy, temp_dp[j]);
}
dp = temp_dp;
}
cout << max_overall_energy << endl;
return 0;
}
import java.util.Scanner;
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int m = sc.nextInt();
int n = sc.nextInt();
int k = sc.nextInt();
int[] a = new int[m];
for (int i = 0; i < m; i++) {
a[i] = sc.nextInt();
}
long[] dp = new long[k + 1];
Arrays.fill(dp, -1);
dp[0] = n;
long maxOverallEnergy = n;
for (int i = 0; i < m; i++) { // 遍历每颗小行星 a[i]
long[] tempDp = Arrays.copyOf(dp, dp.length);
for (int j = 0; j <= k; j++) {
// 来源1: 不采集小行星 a[i]
long energyFromNoCollect = -1;
if (dp[j] > 0) {
energyFromNoCollect = dp[j] - 1;
}
// 来源2: 采集小行星 a[i]
long energyFromCollect = -1;
if (j > 0 && dp[j - 1] > 0) {
energyFromCollect = dp[j - 1] - 1 + a[i];
}
tempDp[j] = Math.max(energyFromNoCollect, energyFromCollect);
maxOverallEnergy = Math.max(maxOverallEnergy, tempDp[j]);
}
dp = tempDp;
}
System.out.println(maxOverallEnergy);
}
}
def main():
m, n, k = map(int, input().split())
a = list(map(int, input().split()))
# dp[j] 表示在当前步骤后,采集了j次的最大能量
dp = [-1] * (k + 1)
dp[0] = n
max_overall_energy = n
for i in range(m): # 遍历每颗小行星 a[i]
temp_dp = list(dp)
for j in range(k + 1):
# 来源1: 不采集小行星 a[i]
energy_from_no_collect = -1
if dp[j] > 0:
energy_from_no_collect = dp[j] - 1
# 来源2: 采集小行星 a[i]
energy_from_collect = -1
if j > 0 and dp[j - 1] > 0:
energy_from_collect = dp[j - 1] - 1 + a[i]
# 新的 dp[j] 是两种来源中的最大值
temp_dp[j] = max(energy_from_no_collect, energy_from_collect)
max_overall_energy = max(max_overall_energy, temp_dp[j])
dp = temp_dp
print(max_overall_energy)
if __name__ == "__main__":
main()
算法及复杂度
- 算法: 动态规划 (0/1 背包变种)
- 时间复杂度:
。其中
m是小行星的数量,k是最大采集次数。外层循环遍历m颗小行星,内层循环遍历k+1种采集状态。 - 空间复杂度:
。我们使用了大小为
k+1的一维数组来存储 DP 状态。
查看14道真题和解析