【状态dp】电车加油问题

题目描述

你正在驾驶一辆电动车从上海前往北京,途中在距离上海x1, x2, …, xn处有充电站。

由于等待时间c和充电速度g的不同,在充电站xi处为电动车充电k公里的电量需要ci + kgi分钟。

你的车有足够的容量一次充满电后行驶400公里。假设车辆在上海的第一个充电站x1开始时电池电量为0,xn是你在北京的目的地。

设计一个高效的算法,找出你应该在哪里停下来,在充电站花费的时间最少。

样例

Input:

x = [0, 100, 300, 600, 800, 1000]
c = [0, 10, 0, 20, 10, 0]
g = [0.05, 0.2, 0.1, 0.2, 0.1, 0]

Output:

 [20,0,30,40,30,0]

在提供的示例中,共有6个充电站。目标是从第一个站点驾驶到最后一个站点,优化在每个站点的花费时间。输出详细说明了在每个站点花费的时间,旨在最小化整个旅程中的总时间。例如,在第一个站点花费的时间计算为0 + 400 * 0.05 = 20分钟。你应该有一个名为timeSpent的函数,用来接收距离x(List[int])、等待时间c(List[int])、充电速度g(List[int])的信息,并返回在每个站点花费的时间t(List[int])。

题解 ——by: GoesM

思路

考虑油量状态最高为400,所以可以将其纳入dp的状态信息中,期待最终的带常数的复杂度约为O(400*n)

线性复杂度在dp问题中基本够用,所以继续延续这个思路:

dp更新等式
  • dp[i][j] 表示到达 i点,油量剩余为j的最优解
  • 每个状态走到下一站的时候只有两种选择:本地加油,或者不加油
  • dp[i][j+k] = min(dp[i][j+k], dp[i][j]+c[i]+k*g[i]) : 在位置i剩余油量为j时,加k公里油
  • 完成dp等式构建√
难点

发现本题的dp构思并不难,难在需要记录dp更新的路径,所以就有了下面很丑的代码

INF = int(1e9)
def timeSpent(x, c, g):
    n = len(x)
    dp = [[INF] * 401 for _ in range(n)]  # 初始化动态规划数组,范围从0到400
    nex = [[(-1,-1)] * 401 for _ in range(n)]  # 记录dp[i][j]是由哪个状态更新的
    
    # 初始化第一个站点
    for j in range(401):
        if j==0:
            dp[0][j] = 0
        else:
            dp[0][j] = c[0] + j * g[0]
            nex[0][j] = (0,0)
    
    # 动态规划递推
    for i in range(1, n): # 想要到i
        distance = x[i] - x[i-1]
        for j in range(401): # 在i-1时的油量
            if(j<distance): # 不够
                pass
            else: # 足够
                dp[i][j-distance] = dp[i-1][j] # 无需加油
                nex[i][j-distance] = (i-1,j) # 状态路径
        for j in range(401):
            if(dp[i][j] < INF ):
                for k in range(401): # 在当前状态下,加k公里油
                    if( k+j > 400 ):
                        break
                    else:
                        if(dp[i][j+k]>dp[i][j] + c[i]+k*g[i] ):
                            dp[i][j+k] = min(dp[i][j+k], dp[i][j]+c[i]+k*g[i])
                            nex[i][j+k] = (i,j)
                        else:
                            pass
            else: # 当前状态不存在,跳过
                pass
    
    # 选取最佳结果的那个最终状态
    now_state = (0,0)
    minx = INF
    for i in range(401):
        if (dp[n-1][j]< minx):
            minx = dp[n-1][j]
            now_state = (n-1,j)
        else:
            pass
    
    t = [0] * n
    
    while( now_state[0]!=-1 ):
        pos = now_state[0]
        left = now_state[1]
        nex_state = nex[pos][left]
        nex_pos = nex_state[0]
        nex_left = nex_state[1]
        if(nex_pos==pos-1):
            pass
        else: # 说明加油了
            t[pos] += c[pos]+(left-nex_left)*g[pos]
        now_state = nex_state
    # print(int(round(minx))) # check if sum is correct
    for i in range(len(t)):
        t[i] = round(t[i])
    return t




日常反思,为什么我高中的时候学不会dp啊,简直对自己无语了…

  • 6
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

GoesM

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值