Leetcode 956. Tallest Billboard 背包DP(从优化问题等价变换得角度解释)

题意:

  • n n n个小铁棒,第 i i i个棒长为 l i l_i li,你希望用这些小铁棒拼接出两个一样长得大铁棒,希望这两个铁棒得长度尽可能的长,要求每个小棒至多用一次
  • 数据范围: n ≤ 20 n \le 20 n20 l i ≤ 1000 l_i \le 1000 li1000, ∑ i l i ≤ 5000 \sum_i l_i \le 5000 ili5000

思路:

  • 这个问题的话,我们乍一看就和背包问题的题面非常像,基本思路就是怎么把优化问题表达成一个背包问题优化的形式。

背包优化问题回顾

  • 为了更好的说明,我们先简单回顾一下背包问题的优化问题,考虑01背包问题,我们的优化问题有如下形式:
    max ⁡ x i ∑ i x i v i s . t . ∑ i x i w i ≤ W x i ∈ { 0 , 1 } \max_{x_i} \sum_i x_iv_i\\ s.t. \quad \sum_i x_iw_i \le W\\ x_i \in \{0, 1\} ximaxixivis.t.ixiwiWxi{0,1}
  • 这个式子很好理解, x i x_i xi就是我们的要优化的决策变量,也就是第 i i i个物品放或者不放,所以它只有两种取值0或者1; v i v_i vi就是我们第 i i i个物品的价值, w i w_i wi就是我们第 i i i个物品的重量, W W W就是背包的容量
  • 当然在上面也会有很多小的变形,比如让约束中的不等号变成等号(如找零钱问题), x i x_i xi的取值范围变化(完全背包或者多重背包),最优解问题转成可行解问题(如能否能找到一种方法,恰好填满包)等等

优化问题转换

  • 我们回到我们遇到的这个问题,首先我们形式化一下这个问题:
    max ⁡ x i , y i ∑ i x i l i s . t . ∑ i x i l i = ∑ i y i l i x i , y i ∈ { 0 , 1 } , x i + y i ≤ 1 \max_{x_i, y_i} \sum_i x_il_i\\ s.t. \quad \sum_i x_il_i = \sum_i y_il_i\\ x_i, y_i \in \{0, 1\}, x_i + y_i \le 1 xi,yimaxixilis.t.ixili=iyilixi,yi{0,1},xi+yi1
  • 我形式化的方法是引入两个决策变量 x i x_i xi y i y_i yi,前者表示第 i i i个小棒接入第一个长棒,后者表示 i i i个小棒是否接入第二个。然后我们要求 x i x_i xi y i y_i yi至多有一个为1,所以有了上面的约束。
  • 这种形式化方法是我觉得一开始比较直观的定义,当然如果有更好的话可以给出更好的定义,这样可能更容易找到解法,我的这种为了弄够求解,这里还需要对上述问题进行变换。
  • 对第一个约束条件我们简单变换一下就得到:
    ∑ i ( x i − y i ) l i = 0 \sum_i (x_i - y_i) l_i = 0 i(xiyi)li=0
  • 然后我们换元一下,令 z i = x i − y i z_i = x_i - y_i zi=xiyi,那么这个约束就变为 ∑ i z i l i = 0 \sum_i z_i l_i = 0 izili=0
  • 这个形式看上去就和我们刚才说的背包问题的约束基本一样了(刚才提到过小于等于或者等于约束都是可以用背包求解)
  • 那么我们进一步来看把原来两个决策变量变换成一个是否和原来的优化问题等价呢?
  • 这里就有一个很好的性质了,由于 x i x_i xi y i y_i yi其实只有三种可能出现的情况: ( 0 , 1 ) , ( 1 , 0 ) , ( 0 , 0 ) (0, 1), (1, 0), (0, 0) (0,1),(1,0),(0,0),这三种情况对应的 z i z_i zi恰好是不同的也就是 { − 1 , 1 , 0 } \{-1, 1, 0\} {1,1,0}三种情况,也就是说我们找到这个映射是在 x i x_i xi y i y_i yi定义域上的一个一一映射,因此我们只需要约束 z i ∈ { − 1 , 1 , 0 } z_i \in \{-1, 1, 0\} zi{1,1,0},那么就可以没有遗漏也没有多余的包含了原始问题的可行域(满足约束条件的所有取值集合)
  • 好了,这样我们就把约束条件变换好了,接下来我们看优化目标,我们要把其中的 x i x_i xi换成 z i z_i zi这样就完全变成对 z i z_i zi的优化了。
  • 其实跟根据刚才说的映射关系,我们很容易就得到一个形式不是特别好看的定义,就是如果 z i = 1 z_i = 1 zi=1,我们就把它加到优化目标里,否则不加,形式化定义就有:
    max ⁡ z i ∑ i l i ⋅ I ( z i = 1 ) \max_{z_i} \sum_i l_i \cdot I(z_i = 1)\\ zimaxiliI(zi=1)
  • 其中 I ( ⋅ ) I(\cdot) I()是一个指示函数,当条件满足时为1,不满足时为0,有了这个形式我们其实就可以做dp了,不过我个人感觉形式不是特别的美观,所以又稍微调整了一下这个形式
  • 根据映射关系,我们不难发现,有如下结论:
    ∑ i ∣ z i ∣ l i = ∑ i x i l i + ∑ i y i l i = 2 ∑ i x i l i \sum_i |z_i| l_i = \sum_i x_i l_i + \sum_i y_i l_i = 2\sum_i x_i l_i izili=ixili+iyili=2ixili
  • 因为常数不影响优化结果,因此我们可以把优化问题变成如下形式:
    max ⁡ z i ∑ i ∣ z i ∣ l i s . t . ∑ i z i l i = 0 z i ∈ { − 1 , 0 , 1 } \max_{z_i} \sum_i |z_i| l_i\\ s.t. \quad \sum_i z_il_i = 0\\ z_i \in \{-1, 0, 1\} zimaxizilis.t.izili=0zi{1,0,1}
  • 这样我们就很容易用背包问题进行求解了,注意约束条件存在负数,实现的时候做了一个加了常数保证索引范围落在自然数范围

实现

class Solution {
public:
    int tallestBillboard(vector<int>& rods) {
        int s = 0;
        for (auto it : rods){
            s += it;
        }
        vector<vector<int>> dp(2, vector<int>((s + 1) << 1, -1));
        dp[0][s] = 0;
        for (int i = 0; i < rods.size(); i++){
            int now = i & 1, next = (i & 1) ^ 1;
            for (int j = 0; j < dp[now].size(); j++){
                if (dp[now][j] == -1)
                    continue;
                dp[next][j] = max(dp[now][j], dp[next][j]);
                dp[next][j + rods[i]] = max(dp[next][j + rods[i]], dp[now][j] + rods[i]);
                dp[next][j - rods[i]] = max(dp[next][j - rods[i]], dp[now][j] + rods[i]);
            }
        }
        return dp[rods.size() & 1][s] >> 1;
    }
};

你可能感兴趣的:(ACM_DP,算法题,Leetcode,DP,背包问题,面试题)