动态规划

动态规划

动态规划思路

  • 刻画一个最优解的结构特征

  • 递归定义最优解的值

  • 计算最优解的值, 通常采取自低向上的方法

  • 利用计算出的信息构造一个最优解

钢条切割问题

给定一段长度为n英寸的钢条和一个价格表 p i p_i pi( i = 1 , 2 , ⋯   , n i = 1, 2,\cdots,n i=1,2,,n), 求切割钢条方案, 使得销售收益 r n r_n rn 最大。注意,如果长度为n英寸的钢条的价格 p i p_i pi足够大,最优解可能就是完全不需要切割

  • 刻画一个最优解的结构特征

    将钢条从左边切割长度为 i i i的一段, 只对右边剩下长度为 n − i n-i ni的一段继续切割,对左边的一段不再切割。

  • 递归定义最优解的值

    r n = max ⁡ 1 ≤ i ≤ n ( p i + r n − 1 ) r_n = \max\limits_{1\leq i \leq n}(p_i + r_{n-1}) rn=1inmax(pi+rn1)

  • 计算最优解的值

    def cut_rot(p: list) -> int:
        n = len(p)
        r = [0] * (n+1)
        for j in range(1, n+1):
            q = -1
            for i in range(j + 1):
                q = max(q, p[i] + r[j - i])
            r[j] = q
        return r[n]
    
  • 利用计算出的信息构造一个最优解

    def cut_rot(p: list):
        n = len(p)
        r = [0] * (n+1)
        s = [0] * (n+1)
        for j in range(1, n+1):
            q = -1
            for i in range(j+1):
                if q < p[i] + r[j - i]:
                    q = p[i] + r[j - i]
                    s[j] = i
                r[j] = q
        return (r[n], s[n])
    

活动选择问题

假定有 n n n个活动的集合 S = { a 1 , a 2 , ⋯   , a n } S=\{a_1,a_2,\cdots,a_n\} S={a1,a2,,an},这些活动使用同一个资源,而这个活动在某个时刻只能供一个活动使用。每个活动 a i a_i ai都有一个开始时间 s i s_i si和一个结束时间 f i f_i fi,其中 0 ≤ s i < f i < ∞ 0\leq s_i<f_i<\infty 0si<fi<。如果被选中,任务 a i a_i ai 发生在半开区间 [ s i , f i ) [s_i, f_i) [si,fi)期间。如果两个活动 a i a_i ai a j a_j aj 满足 [ s i , f i ) [s_i, f_i) [si,fi) [ s j , f i ) [s_j, f_i) [sj,fi)不重叠,则称它们是兼容的。在活动选择问题中,我们希望选出一个最大兼容集。假定活动已按结束时间的单调递增顺序排序:

f 1 ≤ f 2 ≤ f 3 ≤ ⋯ ≤ f n − 1 ≤ f n f_1\leq f_2\leq f_3\leq \cdots\leq f_{n-1}\leq f_n f1f2f3fn1fn

解法一

  • 刻画一个最优解的结构特征

    假设 S i j S_{ij} Sij表示在活动 a i a_i ai结束之后, a j a_j aj开始之前的所有活动的集和,记 A i j A_{ij} Aij S i j S_{ij} Sij 中活动的最大兼容子集且 a k ∈ A i j a_k\in A_{ij} akAij ,于是可以得到两个子问题: 寻找 S i k S_{ik} Sik中的兼容子集,寻找 S k j S_{kj} Skj中的兼容子集。令 A i k = A i j ⋂ S i k A_{ik} = A_{ij} \bigcap S_{ik} Aik=AijSik A k j = A i j ∪ a k ∪ A k j A_{kj} = A_{ij} \cup {a_k}\cup A_{kj} Akj=AijakAkj, A i j = A i k ∪ a k ∪ A k j A_{ij} = A_{ik}\cup{a_k}\cup A_{kj} Aij=AikakAkj
    于是得到
    ∣ A i j ∣ = ∣ A i k ∣ + ∣ A k j ∣ + 1 |A_{ij}| = |A_{ik}| + |A_{kj}| + 1 Aij=Aik+Akj+1

  • 递归定义最优解的值

c [ i , j ] = { 0 , S i j = ∅ c [ i , k ] + c [ k , j ] + 1 , S i j ≠ ∅ c_{[i,j]} = \begin{cases} 0 ,S_{ij} = \empty \\c_{[i,k]} + c_{[k,j]}+1 , S_{ij} \neq \empty\end{cases} c[i,j]={0,Sij=c[i,k]+c[k,j]+1,Sij=

  • 计算最优解的值

    def act_sel(s: list[int], f: list[int]):
        n = len(s)
        c = [[0 for j in range(n+2)] for i in range(n+1)]
        for j in range(1, n+2):
            for i in range(j):
                for k in range(i+1, j):
                    if s[k] > s[i] and f[k] < s[j]:
                        c[i][j] = c[i][k] + c[k][j] + 1
        return c[0][n+1]
    
  • 利用计算出的信息构造一个最优解

    def act_sel(s: list[int], f: list[int]):
        n = len(s)
        r = []
        c = [[0 for j in range(n+2)] for i in range(n+1)]
        for j in range(1, n+2):
            for i in range(j):
                for k in range(i+1, j):
                    if s[k] > s[i] and f[k] < s[j]:
                        c[i][j] = c[i][k] + c[k][j] + 1
                        r.append(k)
        return (c[0][n+1], r)
    

解法二

  • 刻画一个最优解的结构特征

    假设 S S S为所有活动的集和,若 a K ∈ S a_K \in S aKS ,那么可以对 a K a_K aK执行两种操作:选和不选,定义 A k − 1 A_{k-1} Ak1 a k a_k ak之前在此模式下执行的最优解,当不选择 a k a_k ak时,显然 ∣ A k ∣ = ∣ A k − 1 ∣ |A_k| = |A_{k-1}| Ak=Ak1,当选择 a k a_k ak时,记 a m a_m am为结束时间最接近但小于 a k a_k ak开始时间的活动,由此可以推出 ∣ A k ∣ = ∣ A m ∣ + 1 |A_k| = |A_m| + 1 Ak=Am+1

  • 递归定义最优解的值

    c i = max ⁡ 0 ≤ m < i ( c i − 1 , c m + 1 ) c_{i} = \max\limits_{0\leq m<i}(c_{i-1}, c_{m} + 1) ci=0m<imax(ci1,cm+1)

  • 计算最优解的值

    def act_sel(s: list[int], f: list[int]):
        n = len(s)
        c = [0] * (n+1)
        for i in range(1, n+1):
            j = i - 1
            pos = s[j]
            while f[j - 1] > pos:
                j -= 1
            c[i] = max(c[i - 1], c[j-1] + 1)
        return c[n]
    
  • 利用计算出的信息构造一个最优解

    def act_sel(s: list[int], f: list[int]):
        n = len(s)
        c = [0] * (n+1)
        s = []
        for i in range(1, n+1):
            j = i - 1
            pos = s[j]
            while f[j - 1] > pos:
                j -= 1
            c[i] = max(c[i - 1], c[j-1] + 1)
            if c[i-1] > c[j - 1] + 1:
                c[i] = c[i-1]
            else:
                c[i] = c[j-1] + 1
                s.append(i)
        return (c[n], s)
    

0-1背包问题

一个正在抢劫商店的小偷发现了 n n n 件商品,第 i i i件商品价值 v i v_i vi美元,重 w i w_i wi磅, v i v_i vi w i w_i wi都是整数。这个小偷希望拿走价值尽量高的商品,但他的背包最多容纳 w w w磅重的商品, w w w是一个整数。他应该拿哪些商品?

  • 刻画一个最优解的结构特征

    假设 d p i j dp_{ij} dpij表示 j j j磅第 i i i件商品的最优解,第 i i i件有两种决策:选和不选。

    • 如果从第 i i i件商品考虑,不选第 i i i件商品后,包内最大容纳量为 j j j。在此容纳量下,我们完成了 i − 1 i-1 i1次决策,最优解可表示为 d p ( i − 1 ) j dp_{(i-1)j} dp(i1)j,同理选择第 i i i件商品后,包内最大容纳量为 j − w e i g h t i j-weight_i jweighti在此容纳量下,我们完成了 i − 1 i-1 i1次决策,最优解可表示为 d p ( i − 1 ) ( j − w e i g h t i ) dp_{(i-1)(j-weight_i)} dp(i1)(jweighti)
  • 递归定义最优解的值

    d p i j = max ⁡ ( d p ( i − 1 ) j , d p ( i − 1 ) ( j − w e i g h t i ) + v a l u e i ) dp_{ij} = \max(dp_{(i-1)j},dp_{(i-1) (j - weight_i)} + value_i ) dpij=max(dp(i1)j,dp(i1)(jweighti)+valuei)

  • 计算最优解的值

    def com_sel(value:list[int], weight:list[int], W:int):
        n = len(value)
        dp = [[0 for j in range(W+1)] for i in range(n)]
        #初始化
        for j in range(weight[0], W+1):
            dp[0][j] = value[0]
        for j in range(1, W+1):
            for i in range(1, n):
                dp[i][j] = max(dp[i-1][j], dp[i-1][j-weight[i]] + value[i])
        return dp[n-1][W]
    
  • 利用计算出的信息构造一个最优解

    def com_sel(value: list[int], weight: list[int], W: int):
        n = len(value)
        dp = [[0 for j in range(W+1)] for i in range(n)]
        s = []
        # 初始化
        for j in range(weight[0], W+1):
            dp[0][j] = value[0]
        for j in range(1, W):
            for i in range(1, n):
                dp[i][j] = max(dp[i-1][j], dp[i-1][j-weight[i]] + value[i])
        for i in range(1, n):
            if dp[i-1][W-weight[i]] + value[i] < dp[i-1][W]:
                dp[i][W] = dp[i-1][W]
            else:
                dp[i][W] = dp[i-1][W-weight[i]] + value[i]
                s.append(i)
        return dp[n-1][W], s
    

在本章中,每个步骤都要进行一次选择,但选择通常依赖子问题的解。因此,我们通常以一种自底向上的方式求解,先求子问题,然后是较大的问题。然而,是否可以做出局部最优选择,并将该选择加入最优解中,这引出了我们的下一章—贪心选择

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值