动态规划

yxalkaid

背包DP

  • 0-1背包

    1
    2
    3
    4
    5
    6
    7
    8
    9
    def knapsack(capacity, weights, values):
    dp = [0] * (capacity + 1)
    for i in range(len(weights)):
    weight = weights[i]
    value = values[i]
    # 倒序遍历确保每个物品只选一次
    for j in range(capacity, weight - 1, -1):
    dp[j] = max(dp[j], dp[j - weight] + value)
    return dp[-1]
  • 多重背包

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    def knapsack_multiple(capacity, weights, values, counts):
    dp = [0] * (capacity + 1)
    for i in range(len(weights)):
    weight = weights[i]
    value = values[i]
    count = counts[i]
    for j in range(capacity, weight - 1, -1):
    for k in range(1, count + 1):
    if weight * k > j:
    break
    dp[j] = max(dp[j], dp[j - weight * k] + value * k)
    return dp[-1]
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    def knapsack_multiple(capacity, weights, values, counts):
    dp = [0] * (capacity + 1)
    for i in range(len(weights)):
    weight = weights[i]
    value = values[i]
    count = counts[i]
    k = 1
    while k < count:
    # 二进制拆分:将当前k个物品视为01背包的子物品
    for j in range(capacity, weight * k - 1, -1):
    dp[j] = max(dp[j], dp[j - weight * k] + value * k)
    count -= k
    k *= 2
    # 处理剩余无法拆分的cnt个物品
    if count > 0:
    for j in range(capacity, weight * count - 1, -1):
    dp[j] = max(dp[j], dp[j - weight * count] + value * count)
    return dp[-1]
  • 完全背包

    1
    2
    3
    4
    5
    6
    7
    8
    9
    def knapsack_complete(capacity, weights, values):
    dp = [0] * (capacity + 1)
    for i in range(len(weights)):
    weight = weights[i]
    value = values[i]

    # 正序遍历允许重复选择当前物品
    for j in range(weight, capacity + 1):
    dp[j] = max(dp[j], dp[j - weight] + value)
  • 混合背包
    通过组合0-1背包、多重背包和完全背包实现

  • 状态压缩
    capacity和weights除以最大公约数作为新的容量和重量。

区间DP

树形DP

  • 树上背包
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    def dfs(i):
    weight = weights[i]
    value = values[i]
    dp = [0] * (capacity + 1)
    for w in range(weight, capacity + 1):
    dp[w] = value

    for child in children[i]:
    child_dp = dfs(child)
    for j in range(capacity, weight - 1, -1):
    for k in range(j - weight + 1):
    dp[j] = max(dp[j], dp[j - k] + child_dp[k])
    return dp

状压DP

数位DP

DP优化

经典问题

  • 鸡蛋掉落
    f(k, n)表示有k个鸡蛋、n层楼时,最坏情况下的最小尝试次数。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    def superEggDrop(self, k: int, n: int) -> int:

    cache = dict()

    def dp(k, n):
    if n == 0:
    return 0
    if k == 1:
    return n

    item = (k, n)
    if item in cache:
    return cache[item]

    left = 1
    right = n
    while left + 1 < right:
    mid = (left + right) // 2
    broken = dp(k - 1, mid - 1)
    not_broken = dp(k, n - mid)
    if broken == not_broken:
    left = mid
    right = mid
    else:
    if broken > not_broken:
    right = mid
    else:
    left = mid

    res = n
    for x in range(left, right + 1):
    res = min(res, 1 + max(dp(k - 1, x - 1), dp(k, n - x)))
    cache[item] = res
    return res

    return dp(k, n)