给定一条有路灯的街道,找出点亮整条街道的最低成本

Given a street with lamps, finding minimum cost to light entire street

这是一个我正在苦苦挣扎的练习。

一条街有nlamps,第i第lamp在位置.

最初所有 lamp 都关闭。对于第 i-th lamp 你有 3 个选项:

  1. 保持开关状态 关闭 并且不支付任何费用。
  2. 切换到模式1并支付cost1i。第i-thlamp会点亮位置i.
  3. 切换到模式2并支付cost2i。第i-第lamp会点亮位置ileftRangei 左侧更多位置和 rightRangei 右侧更多位置。例如,如果 i = 5,并且 leftRangei = 3 并且 _rightRange i = 2,那么,将灯切换到模式 2 将点亮位置 2、3、4、5、6 和 7

我需要找到点亮所有 lamp 的最低总成本。 cost1cost2leftRangerightRange都是数组.

这是动态规划的基本练习。

我建议您先将数组转换为一组 (interval, cost) 对。然后问问自己:

  • 我可以使用哪个间隔子集以最低成本覆盖整条街道?

将此问题重新表述为对递归更友好的问题,如下所示:

  • 我可以使用哪个区间子集以最低成本覆盖街道 直到索引 i

这个问题可以这样回答:

  • 在索引 i 之前覆盖街道的最便宜的方法是使用覆盖 i 的灯加上最便宜的覆盖方法街道直到那个灯的左边范围。
    • PLUS 部分可以通过查看之前计算的成本来回答,因为该部分严格小于 i
    • 用哪个灯来覆盖 i 是通过遍历覆盖 i 的所有灯并选择最便宜的灯来确定的。

当你有了这个递归解决方案后,只需按以下顺序回答问题:

  • 我可以使用哪个间隔子集以最低成本覆盖街道 直到索引 1(基本情况)
  • 我可以使用哪个间隔子集以最低成本覆盖街道 直到索引 2(计算涉及查看以前的答案)
  • ...
  • 我可以使用哪个间隔子集以最低成本覆盖街道 直到索引 n(计算涉及查看以前的答案)

这是第一次尝试:

# Sample data
n = 5
cost1 = [ 10, 20, 10, 5, 3 ]
cost2 = [ 15, 25, 10, 10, 30 ]
leftRange = [ 0, 1, 0, 1, 2 ]
rightRange = [ 2, 0, 1, 1, 0 ]

class LightRange:
    def __init__(self, light, mode, left, right, cost):
        self.light = light
        self.mode = mode
        self.left = left
        self.right = right
        self.cost = cost

    def doesCover(self, i):
        return self.left <= i and i <= self.right


# Transform input data into a set of LightRanges
ranges = []
for i in range(n):
    ranges.append(LightRange(i, 1, i, i, cost1[i]))
    ranges.append(LightRange(i, 2, i - leftRange[i], i + rightRange[i], cost2[i]))


# For convenience: Create an index telling us what ranges covers index i
rangesCoveringI = []
for i in range(n):
    rangesCoveringI.append([])
    for r in ranges:
        if r.doesCover(i):
            rangesCoveringI[i].append(r)


# Declare two variables holding our solution:
# costToCoverI[i] = the lowest cost for lighting up the street from 0 to i
costToCoverI = []
# rangeUsedToCoverI[i] = range used for covering i when lighting up the street from 0 to i
rangeUsedToCoverI = []

# Populate the solution variables going from left to right on the street
for i in range(n):
    # Walk through ranges covering i and determine the cheapest one to use
    bestCostToCoverI = None
    bestRangeToCoverI = None

    for r in rangesCoveringI[i]:
        # Compute cost associated with using this range
        costThisRange = r.cost
        costLeftOfThisRange = 0 if r.left <= 0 else costToCoverI[r.left - 1]
        cost = costThisRange + costLeftOfThisRange

        # Cheapest so far?
        if bestCostToCoverI is None or cost < bestCostToCoverI:
            bestCostToCoverI = cost
            bestRangeToCoverI = r
    
    costToCoverI.append(bestCostToCoverI)
    rangeUsedToCoverI.append(bestRangeToCoverI)

# Final solution: costToCoverI[n - 1] = the lowest cost for lighting up the street from 0 to n
print(f"Total cost: {costToCoverI[n - 1]}")

# Backtrack to figure out final mode selection
i = n - 1
while i > 0:
    r = rangeUsedToCoverI[i]
    print(f"{r.light} set to mode {r.mode}")
    i = r.left - 1

输出:

Total cost: 23
4 set to mode 1
3 set to mode 1
0 set to mode 2

N.B.: 未经测试的代码。

  • 尝试一下,尝试理解每一步。
  • 调试您发现的任何问题。
  • 如果有不明白的地方,请在评论中提出后续问题。