#P2301. 第2题-穿越城市
          
                        
                                    
                      
        
              - 
          
          
                      1000ms
            
          
                      Tried: 587
            Accepted: 191
            Difficulty: 5
            
          
          
          
                       所属公司 : 
                              华为
                                
            
                        
              时间 :2024年9月13日-国内
                              
                      
          
 
- 
                        算法标签>动态规划          
 
第2题-穿越城市
题意化简
这个题的本质是一个路径规划问题。你有n个街区,每个街区有步行时间a_i,可以选择步行一格或乘坐地铁跳跃1到K个街区,地铁每格花费B时间。你最多可以坐M次地铁,目标是找到从第1个街区到第N个街区的最小时间消耗。
思路
此题是一个经典的动态规划问题,设置dp[i][j]为走到第i个街区并且已经坐了j次地铁的最短时间,转移则分为两种情况,一种是从前一个位置到当前位置不做地铁,一种是坐地铁到当前位置,第二种情况要枚举从前方哪里坐的地铁到现在的位置,并取最小值,初始化则为全不坐地铁的情况
题解
这道题是一个经典的动态规划问题。我们可以将城市的街区视为一个线性序列,使用动态规划来求解从第一个街区到最后一个街区的最短时间。定义动态规划状态 dp[i][j] 表示走到第 i 个街区,并且已经坐了 j 次地铁的最短时间。
动态规划思路
- 
状态定义:
dp[i][j]:表示走到第i个街区,并且使用了j次地铁的最小时间。
 - 
初始化:
dp[0][0] = 0:表示在城市的起点(第0个位置,左侧)时,不需要任何时间。dp[i][0]:仅通过步行到达第i个街区的时间,可以通过前一个街区的时间累加步行所需的时间。
 - 
状态转移:
- 对于每一个街区 
i:- 不坐地铁:从 
i-1走到i,时间为dp[i-1][j] + a[i]。 - 坐地铁:从前面的某个街区 
l坐地铁到当前街区i。这里我们需要枚举所有可能的前一个街区l,l必须在i - k和i - 1之间,以确保我们不超过地铁的最大跨越数量k。 - 每次坐地铁的时间为 
b * (i - l)。 
 - 不坐地铁:从 
 
 - 对于每一个街区 
 - 
最终结果:
- 结果为 
dp[n][j]中的最小值,j可以从0到m,表示我们使用的地铁次数。 
 - 结果为 
 
代码说明
- 常数定义:使用 
const long long inf = 1e18;来表示一个无穷大,便于后续的最小值比较。 - 输入部分:读取街区数量 
n、地铁一次最多可跨越的街区数k、每个街区的步行时间以及地铁的时间b和最多可坐地铁的次数m。 - 动态规划数组:
dp数组初始化为无穷大,只有起始位置dp[0][0]被初始化为0。 - 动态规划计算:根据状态转移方程更新 
dp数组的值。 - 输出结果:在所有使用0到 
m次地铁的情况下,输出最小时间。 
代码
c++
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
const long long inf = 1e18;  // 定义一个足够大的常数,表示无穷大
int main() {
    int n, k, m;  // n为街区数量,k为地铁一次最多可跨越的街区数,m为最多可坐地铁次数
    cin >> n >> k;
    
    vector<int> a(n + 1);  // 保存每个街区的步行时间,从1开始
    for (int i = 1; i <= n; ++i) {
        cin >> a[i];
    }
    
    int b;  // 每次坐地铁的时间
    cin >> b >> m;
    // 初始化dp数组,dp[i][j] 表示到达第 i 个街区使用 j 次地铁的最小时间
    vector<vector<long long>> dp(n + 1, vector<long long>(m + 1, inf));
    dp[0][0] = 0;  // 起点不花时间
    // 动态规划过程
    for (int i = 1; i <= n; ++i) {
        // 仅通过步行到达第 i 个街区的时间
        dp[i][0] = dp[i - 1][0] + a[i];
        
        for (int j = 1; j <= m; ++j) {  // 0到m次地铁
            // 不坐地铁的情况
            dp[i][j] = dp[i - 1][j] + a[i];
            
            // 坐地铁的情况,枚举从前面哪个街区坐地铁
            int last = max(0, i - k);  // 可以坐地铁的最远起始点
            for (int l = last; l < i; ++l) {  // l是前一个街区的索引
                // 更新使用 j 次地铁的最小时间
                dp[i][j] = min(dp[i][j], dp[l][j - 1] + b * (i - l));
            }
        }
    }
    // 输出最小结果
    long long result = inf;  // 初始化结果为无穷大
    for (int j = 0; j <= m; ++j) {
        result = min(result, dp[n][j]);  // 取到达第 n 个街区的最小时间
    }
    
    cout << result << endl;  // 输出最终结果
    return 0;
}
python
n = int(input())
k = int(input())
a = [0] + list(map(int, input().split()))
b = int(input())
m = int(input())
inf = 10**18
dp = [[inf] * (m + 1) for _ in range(n + 1)]
dp[0][0] = 0
for i in range(1 , n + 1):
    dp[i][0] = dp[i - 1][0] + a[i]
    for j in range(1 , m + 1):
        dp[i][j] = dp[i - 1][j] + a[i]
        # 坐地铁,最远坐k站,枚举到底要坐多少站
        last = max(0 , i - k)
        for l in range(last , i):
            dp[i][j] = min(dp[i][j] , dp[l][j - 1] + b * (i - l))
print(min(dp[n]))
java
import java.util.Scanner;
import java.util.Arrays;
public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int k = sc.nextInt();
        int[] a = new int[n + 1];
        for (int i = 1; i <= n; i++) {
            a[i] = sc.nextInt();
        }
        int b = sc.nextInt();
        int m = sc.nextInt();
        
        long inf = (long) 1e18;
        long[][] dp = new long[n + 1][m + 1];
        
        // 初始化 dp 数组
        for (int i = 0; i <= n; i++) {
            Arrays.fill(dp[i], inf);
        }
        dp[0][0] = 0;
        
        // 动态规划过程
        for (int i = 1; i <= n; i++) {
            dp[i][0] = dp[i - 1][0] + a[i];  // 不坐地铁
            for (int j = 1; j <= m; j++) {
                dp[i][j] = dp[i - 1][j] + a[i];  // 不坐地铁
                int last = Math.max(0, i - k);
                for (int l = last; l < i; l++) {  // 坐地铁,最远坐k站,枚举到底要坐多少站
                    dp[i][j] = Math.min(dp[i][j], dp[l][j - 1] + b * (i - l));
                }
            }
        }
        
        // 输出最小结果
        long result = inf;
        for (int j = 0; j <= m; j++) {
            result = Math.min(result, dp[n][j]);
        }
        System.out.println(result);
    }
}
        题目内容
小明需要走路从城市的一端前往另一端。城市可以视为一个长条形,共有N个街区,按顺序排成一列,
每个街区的右侧紧挨着下一个街区的左侧。
初始时,小明位于第1个街区的左侧,他的目标是到达第N个街区的右侧。步行通过第n个街区时,小明需要花费的时间为an。
同时,小明可以选择坐最多M次地铁。每个街区的左侧都有地铁站,每次坐地铁可以穿越前方最少1个,最多k个连续的街区。
坐地铁穿越任何一个街区所需的时间都是一个常数B(如果穿越2个街区,所需的时间是2×B,以此类推),进地铁站、出地铁站、等待地铁均不耗费时间。
输入描述
前两行各包含一个正整数,分别对应 N 和 K。
第三行包含 N 个非负整数,以空格分隔,对应于步行穿过每个街区所消耗的时间。
后两行各包含一个非负整数,分别对应 B 和 M。
1≤N≤10,1≤K≤10,0≤M≤10,以任何方式通过单个街区所需要的时间
(包括所有an以及 B 的值)不超过104。
输出描述
一个整数,表示穿越整个城市花费的最短时间。
样例1
输入
5
1
3 7 5 3 6
0
2
输出
11
说明
总共5个街区,坐地铁每次只能通过1个街区,坐地铁消耗时间为0,最多可以坐2次地铁。
最少消耗的方案为:坐地铁通过第2个和第5个街区,其余街区步行,最终消耗时间为3+0+5+3+0=11。
样例2
输入
5
2
1 2 1 2 2
3
2
输出
8
说明
全程走路,不坐地铁,最终消耗时间为1+2+1+2+2=8。
样例3
输入
10
2
4 1 12 1 6 7 2 4 4 4
3
2
输出
29
说明
坐地铁两次,分别穿越第3个街区以及第5−6个街区,
最终消耗时间为4+1+3(地铁)+1+3×2(地铁)+2+4+4+4=29。