返回顶部

动态规划随笔

整数拆分

给定一个正整数 n ,将其拆分为 k 个 正整数 的和( k >= 2 ),并使这些整数的乘积最大化。

该将给定的正整数拆分成尽可能多的 3,推导过程如下:


01背包(二维数组)

import java.util.*;

public class Main
{
    public static void main(String[] args)
    {
        Scanner sc = new Scanner(System.in);
        int M = sc.nextInt();//物品种类
        int N = sc.nextInt();//背包容量
        int[] weights = new int[M + 1];//物品重量
        int[] values = new int[M + 1];//物品价值
        for(int i = 1;i <= M;i++)
            weights[i] = sc.nextInt();
        for(int i = 1;i <= M;i++)
            values[i] = sc.nextInt();
        //dp[i][j]含义:当选取0-i物品放入容量为j的背包中能获取的最大利益
        int[][] dp = new int[M + 1][N + 1];
        //初始化:当背包容量为0时或物品编号为0时总价值为0,当只能放入编号为1的物品时,从它能放进去往后最大价都为该物品价值
        for(int i = 0;i <= M;i++)
            dp[i][0] = 0;
        for(int i = 0;i <= N;i++)
            dp[0][i] = 0;
        for(int i = 1;i <= N;i++)
        {
            if(i < weights[0])
                continue;
            dp[1][i] = values[0];
        }
        //递推公式:dp[i][j] = max{dp[i - 1][j],dp[i - 1][j - weights[i]] + values[i]}
        //本题dp[i][j]的值是根据左上角和正上方的值推导出来,所以先遍历物品(行),背包(列)都可以
        for(int i = 1;i <= M;i++)
        {
            for(int j = 1;j <= N;j++)
            {
                if(j >= weights[i])
                    dp[i][j] = Math.max(dp[i - 1][j],dp[i - 1][j - weights[i]] + values[i]);
                else dp[i][j] = dp[i - 1][j];
            }
        }
        System.out.println(dp[M][N]);
    }
}

01背包(滚动数组版详细推导过程)

import java.util.*;
 
public class Main
{
    public static void main(String[] args)
    {
        Scanner sc = new Scanner(System.in);
        int M = sc.nextInt();//物品种类
        int N = sc.nextInt();//背包容量
        int[] weights = new int[M + 1];//物品重量
        int[] values = new int[M + 1];//物品价值
        for(int i = 1;i <= M;i++)
            weights[i] = sc.nextInt();
        for(int i = 1;i <= M;i++)
            values[i] = sc.nextInt();
        //dp[j]含义:容量为j的背包放入物品所能获得的最大收益
        int[] dp = new int[N + 1];
        //初始化:递推公式 dp[j] = max{dp[j],dp[j - weights[i]]+values[i]}
        //物品两种状态,放:dp[i][j] = dp[i-1][j - weights[i]+values[i]
        //不放:dp[i][j] = dp[i-1][j]
        //两者都只和dp[i-1],即上一行的数据有关,那么原来的表格可以压缩成两行
        //比如dp[0] = 第一行 dp[1] = 算出的第二行 之后dp[0] = dp[1] dp[1] = 第三行结果
        //还可以进一步压缩,从右往左遍历,dp[j]之前的元素都是还没被更新的上一行结果,那么直接用之前的元素就行
        //这样只需要一行
        //如果从左向右遍历会导致重复,假如背包容量为10,新的物品容量为4,价值为9
        //此时如果不放,最大价值仍是dp[10],而如果放的话就要为它腾出空间,取上一轮的结果比较
        //要与dp[10 - 4] + 9 = dp[6] + 9 比较,注意这里的dp[6]应该是上一轮还没放入此物品的最大利益
        //如果正序遍历,那么这里的dp[6]被更新过了,变成了本轮考虑放入这个物品后的结果,这里假定放入了这个物品
        //当这个dp[6](此时这个dp[6]已经包含了一个9)+9 > dp[10]时,这个物品就要放入,那么dp[10] = dp[6](包含一个9) + 9
        //则dp[10]中包含了2个9,即这个价值为9的物品被放入了两次,价格算错了
        for(int i = 1;i <= M;i++)
        {
            for(int j = N;j >= weights[i];j--)
            {
                dp[j] = Math.max(dp[j],dp[j - weights[i]] + values[i]);
            }
        }
        System.out.println(dp[N]);
    }
}

完全背包状态转移方程推导

对于每个物品,两种选择 放/不放
放(假设放k个,k ≥ 1):dp[i][j] = dp[i - 1][j - k * weights[i]] + k * values[i]
不放:dp[i][j] = dp[i - 1][j]
完全背包状态转移方程:
dp[i][j] = max {dp[i - 1][j],max{ dp[i - 1][j - k * weights[i]] + k * values[i]} }
(1 ≤ k ≤ j / weights[i]) (取值范围由j - k * weights[i] ≥ 0求得)
上述遍历复杂度为O(N3),太高,尝试优化为0(N2),放入时至少放1个,不妨先放一个进去,等价为:
dp[i][j] = max {dp[i - 1][j],max{dp[i - 1][j - weights[i] - k * weights[i]] + k * values[i] + values[i]} }

此时k取值范围变为0≤ k ≤ j / weights[i] - 1
观察式①,发现当k = 0时,dp[i - 1][j - k * weights[i]] + k * values[i] = dp[i - 1][j]
因此式①等价为dp[i][j] = max{dp[i - 1][j - k * weights[i]] + k * values[i]}
0 ≤ k ≤ j / weight[i] 可以看到,该式子消掉了一层遍历!
①与②③等价,因此②与③等价。
令j = j - weights[i],dp[i][j - weights[i]] = max{dp[i - 1][j - weights[i] - k * weights[i]] + k * values[i]}
0 ≤ k ≤ (j - weights[i])/weights[i] = j / weights[i] - 1
观察②④,有相同部分max{dp[i - 1][j - weights[i] - k * weights[i]] + k * values[i]}
②中max{dp[i - 1][j - weights[i] - k * weights[i]] + k * values[i] + values[i]} = max{dp[i - 1][j - weights[i] - k * weights[i] + k * values[i]} + values[i]
替换得: dp[i][j] = max{dp[i - 1][j],dp[i][j - weights[i]]},消除了k且复杂度变回O(N2)!

因此得到二维完全背包公式:
dp[i][j] = max{dp[i - 1][j],dp[i][j - weights[i]]},j ≥ weights[i] 和0 - 1背包仅仅相差一个 -1
否则 dp[i][j] = dp[i - 1][j]

之所以一维背包不逆序:

需要用到的是正上方以及左侧的信息,如果逆序的话,那么左边所用的信息就没有经过更新了

可以先遍历背包或先遍历物品的原因:完全背包递推公式每一项使用的是左边以及正上方的信息,先遍历背包等价于一列一列赋值,左边和正上方信息足够;而0-1背包需要的是左上方和正上方信息(上一行信息),必须保证行遍历。

posted @ 2023-12-10 23:41  SuZ1  阅读(7)  评论(0编辑  收藏  举报