牛客题解 | 过河

题目## 题目

题目链接

解题思路

这是一道动态规划优化的题目。关键优化点在于对状态空间的压缩:

  1. 直接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(LT),显然超时
  2. 状态空间优化:

    • 观察到石子数 M M M 最多只有 100 100 100
    • 两个石子间距离如果大于 T T T,中间的点可以压缩
    • 对于距离大于 T T T 的情况,只需保留 m o d   T + T mod\ T + T mod T+T 的长度
  3. 动态规划设计:

    • 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[ij]+idx[ij]) 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)

题目链接

解题思路

  1. 使用 d p dp dp 数组记录到达每个位置能获得的最大积分
  2. 对于每个位置 i i i,遍历其能跳到的所有位置 j j j
  3. 状态转移方程: 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])
  4. 需要先判断位置是否可达,使用一个布尔数组记录可达性
  5. 最后检查终点是否可达,如果可达则返回 d p [ n − 1 ] dp[n-1] dp[n1],否则返回 − 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的数组

这道题是跳跃游戏的变体,除了要判断能否到达终点外,还需要计算最大积分。关键点在于:

  1. 需要同时维护两个数组:

    • c a n _ r e a c h can\_reach can_reach 数组记录每个位置是否可达
    • d p dp dp 数组记录到达每个位置的最大积分
  2. 状态转移的过程:

    • 只有当前位置 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])
  3. 特殊情况处理:

    • 数组长度为 0 0 0 时返回 − 1 -1 1
    • 数组长度为 1 1 1 时直接返回 n u m s [ 0 ] nums[0] nums[0]
    • 如果终点不可达,返回 − 1 -1 1

这种方法能够保证找到所有可能的跳跃路径,并在其中选择积分最大的一条。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值