题目## 题目
解题思路
这是一道动态规划优化的题目。关键优化点在于对状态空间的压缩:
-
直接DP会超时:
- 如果直接定义 d p [ i ] dp[i] dp[i] 为到达第 i i i 点的最小石子数
- 由于 L L L 可达 1 0 9 10^9 109,复杂度将达到 O ( L ∗ T ) O(L*T) O(L∗T),显然超时
-
状态空间优化:
- 观察到石子数 M M M 最多只有 100 100 100 个
- 两个石子间距离如果大于 T T T,中间的点可以压缩
- 对于距离大于 T T T 的情况,只需保留 m o d T + T mod\ T + T mod T+T 的长度
-
动态规划设计:
- d p [ i ] dp[i] dp[i] 表示到达压缩后第 i i i 个位置需要的最小石子数
- 转移方程: d p [ i ] = m i n ( d p [ i ] , d p [ i − j ] + i d x [ i − j ] ) dp[i] = min(dp[i], dp[i-j] + idx[i-j]) dp[i]=min(dp[i],dp[i−j]+idx[i−j]), j ∈ [ S , T ] j \in [S,T] j∈[S,T]
- i d x idx idx 数组标记压缩后的位置是否有石子
代码
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int MAXN = 2005;
const int MAXM = 105;
const int INF = 0x7f7f7f7f;
class Solution {
private:
int dp[MAXN];
int idx[MAXN + 1];
int stone[MAXM];
public:
Solution() {
memset(dp, 0, sizeof(dp));
memset(idx, 0, sizeof(idx));
memset(stone, 0, sizeof(stone));
}
int solve(int L, int S, int T, int M, int stones[]) {
// 复制石头位置
for(int i = 1; i <= M; i++) {
stone[i] = stones[i-1];
}
// 压缩状态空间
sort(stone + 1, stone + M + 1);
stone[++M] = L; // 添加终点
int l = 0; // 压缩后的长度
memset(idx, 0, sizeof(idx));
for(int i = 1; i <= M; i++) {
int dis = stone[i] - stone[i - 1];
if(dis > T) {
l += dis % T + T;
} else {
l += dis;
}
idx[l] = 1;
}
// 动态规划求解
int Length = l + T;
memset(dp, INF, sizeof(dp));
dp[0] = 0;
for(int i = 1; i < Length; i++) {
for(int j = S; j <= T; j++) {
if(i >= j) {
dp[i] = min(dp[i], dp[i - j] + idx[i - j]);
}
}
}
// 寻找最优解
int result = INF;
for(int i = l; i < Length; i++) {
result = min(result, dp[i]);
}
return result;
}
};
int main() {
int L, S, T, M;
scanf("%d", &L);
scanf("%d%d%d", &S, &T, &M);
int stones[MAXM];
for(int i = 0; i < M; i++) {
scanf("%d", &stones[i]);
}
Solution solution;
printf("%d\n", solution.solve(L, S, T, M, stones));
return 0;
}
import java.util.*;
public class Main {
static class Solution {
private static final int MAXN = 2005;
private static final int MAXM = 105;
private static final int INF = 0x7f7f7f7f;
private int[] dp = new int[MAXN];
private int[] idx = new int[MAXN + 1];
private int[] stone = new int[MAXM];
public int solve(int L, int S, int T, int M, int[] stones) {
// 复制石头位置
for(int i = 1; i <= M; i++) {
stone[i] = stones[i-1];
}
// 压缩状态空间
Arrays.sort(stone, 1, M + 1);
stone[++M] = L; // 添加终点
int l = 0; // 压缩后的长度
Arrays.fill(idx, 0);
for(int i = 1; i <= M; i++) {
int dis = stone[i] - stone[i - 1];
if(dis > T) {
l += dis % T + T;
} else {
l += dis;
}
idx[l] = 1;
}
// 动态规划求解
int Length = l + T;
Arrays.fill(dp, INF);
dp[0] = 0;
for(int i = 1; i < Length; i++) {
for(int j = S; j <= T; j++) {
if(i >= j) {
dp[i] = Math.min(dp[i], dp[i - j] + idx[i - j]);
}
}
}
// 寻找最优解
int result = INF;
for(int i = l; i < Length; i++) {
result = Math.min(result, dp[i]);
}
return result;
}
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int L = sc.nextInt();
int S = sc.nextInt();
int T = sc.nextInt();
int M = sc.nextInt();
int[] stones = new int[M];
for(int i = 0; i < M; i++) {
stones[i] = sc.nextInt();
}
Solution solution = new Solution();
System.out.println(solution.solve(L, S, T, M, stones));
}
}
from typing import List
import sys
class Solution:
def __init__(self):
self.MAXN = 2005
self.MAXM = 105
self.INF = 0x7f7f7f7f
self.dp = [0] * self.MAXN
self.idx = [0] * (self.MAXN + 1)
self.stone = [0] * self.MAXM
def solve(self, L: int, S: int, T: int, M: int, stones: List[int]) -> int:
# 复制石头位置
for i in range(M):
self.stone[i + 1] = stones[i]
# 压缩状态空间
self.stone[1:M+1] = sorted(self.stone[1:M+1])
M += 1
self.stone[M] = L # 添加终点
l = 0 # 压缩后的长度
self.idx = [0] * (self.MAXN + 1)
for i in range(1, M + 1):
dis = self.stone[i] - self.stone[i - 1]
if dis > T:
l += dis % T + T
else:
l += dis
self.idx[l] = 1
# 动态规划求解
Length = l + T
self.dp = [self.INF] * self.MAXN
self.dp[0] = 0
for i in range(1, Length):
for j in range(S, T + 1):
if i >= j:
self.dp[i] = min(self.dp[i], self.dp[i - j] + self.idx[i - j])
# 寻找最优解
result = self.INF
for i in range(l, Length):
result = min(result, self.dp[i])
return result
def main():
input = sys.stdin.buffer.readline
L = int(input())
S, T, M = map(int, input().split())
stones = list(map(int, input().split()))
solution = Solution()
print(solution.solve(L, S, T, M, stones))
if __name__ == "__main__":
main()
算法及复杂度
- 算法:动态规划 + 状态空间压缩
- 时间复杂度: O ( M T ) \mathcal{O}(MT) O(MT), M M M 为石子数, T T T 为最大跳跃距离
- 空间复杂度: O ( M T ) \mathcal{O}(MT) O(MT)
解题思路
- 使用 d p dp dp 数组记录到达每个位置能获得的最大积分
- 对于每个位置 i i i,遍历其能跳到的所有位置 j j j
- 状态转移方程: d p [ j ] = m a x ( d p [ j ] , d p [ i ] + n u m s [ j ] ) dp[j] = max(dp[j], dp[i] + nums[j]) dp[j]=max(dp[j],dp[i]+nums[j])
- 需要先判断位置是否可达,使用一个布尔数组记录可达性
- 最后检查终点是否可达,如果可达则返回 d p [ n − 1 ] dp[n-1] dp[n−1],否则返回 − 1 -1 −1
代码
#include <iostream>
#include <vector>
using namespace std;
int maxScore(vector<int>& nums) {
int n = nums.size();
if (n == 0) return -1;
if (n == 1) return nums[0];
vector<bool> can_reach(n, false); // 记录位置是否可达
vector<int> dp(n, 0); // 记录到达每个位置的最大积分
can_reach[0] = true;
dp[0] = nums[0];
// 遍历每个位置
for (int i = 0; i < n; i++) {
if (!can_reach[i]) continue;
// 从当前位置能跳到的所有位置
for (int j = i + 1; j <= min(i + nums[i], n - 1); j++) {
can_reach[j] = true;
dp[j] = max(dp[j], dp[i] + nums[j]);
}
}
return can_reach[n-1] ? dp[n-1] : -1;
}
int main() {
int n;
cin >> n;
vector<int> nums(n);
for (int i = 0; i < n; i++) {
cin >> nums[i];
}
cout << maxScore(nums) << endl;
return 0;
}
import java.util.*;
public class Main {
public static int maxScore(int[] nums) {
int n = nums.length;
if (n == 0) return -1;
if (n == 1) return nums[0];
boolean[] canReach = new boolean[n];
int[] dp = new int[n];
canReach[0] = true;
dp[0] = nums[0];
for (int i = 0; i < n; i++) {
if (!canReach[i]) continue;
for (int j = i + 1; j <= Math.min(i + nums[i], n - 1); j++) {
canReach[j] = true;
dp[j] = Math.max(dp[j], dp[i] + nums[j]);
}
}
return canReach[n-1] ? dp[n-1] : -1;
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int[] nums = new int[n];
for (int i = 0; i < n; i++) {
nums[i] = sc.nextInt();
}
System.out.println(maxScore(nums));
}
}
def maxScore(nums):
n = len(nums)
if n == 0:
return -1
if n == 1:
return nums[0]
can_reach = [False] * n # 记录位置是否可达
dp = [0] * n # 记录到达每个位置的最大积分
can_reach[0] = True
dp[0] = nums[0]
# 遍历每个位置
for i in range(n):
if not can_reach[i]:
continue
# 从当前位置能跳到的所有位置
for j in range(i + 1, min(i + nums[i] + 1, n)):
can_reach[j] = True
dp[j] = max(dp[j], dp[i] + nums[j])
return dp[n-1] if can_reach[n-1] else -1
n = int(input())
nums = list(map(int, input().split()))
print(maxScore(nums))
算法及复杂度
- 算法:动态规划
- 时间复杂度: O ( n 2 ) \mathcal{O}(n^2) O(n2) - 对于每个位置都可能需要遍历后面的所有位置
- 空间复杂度: O ( n ) \mathcal{O}(n) O(n) - 需要两个长度为n的数组
这道题是跳跃游戏的变体,除了要判断能否到达终点外,还需要计算最大积分。关键点在于:
-
需要同时维护两个数组:
- c a n _ r e a c h can\_reach can_reach 数组记录每个位置是否可达
- d p dp dp 数组记录到达每个位置的最大积分
-
状态转移的过程:
- 只有当前位置 i i i 可达时才进行跳跃
- 对于可以跳到的每个位置 j j j,更新其可达性和最大积分
- 最大积分的更新公式: d p [ j ] = m a x ( d p [ j ] , d p [ i ] + n u m s [ j ] ) dp[j] = max(dp[j], dp[i] + nums[j]) dp[j]=max(dp[j],dp[i]+nums[j])
-
特殊情况处理:
- 数组长度为 0 0 0 时返回 − 1 -1 −1
- 数组长度为 1 1 1 时直接返回 n u m s [ 0 ] nums[0] nums[0]
- 如果终点不可达,返回 − 1 -1 −1
这种方法能够保证找到所有可能的跳跃路径,并在其中选择积分最大的一条。