动态规划背包问题
Posted Vincent丶
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了动态规划背包问题相关的知识,希望对你有一定的参考价值。
背包问题无疑是最经典的dp问题,其次就是关于字符串匹配问题,数组最长递增(减)序列长度等等。背包问题变体很多。
动态规划问题实际上与备忘录式深搜有些类似。
1. 0-1背包
题目:
有n个重量和价值分别为wi, vi的物品。从这些物品中挑选出总重量不超过W的物品,求所有挑选方案中价值总和的最大值。
限制条件:
1<= n <= 100; 1<= wi, vi <= 100; 1 <= W <= 1000
样例:
输入
n = 4 (w, v) = {(2,3),(1,2),(3,4),(2,2)} W = 5
输出
7 (选择第0,1,3号物品)
先从深搜开始,仔细分析问题,就会发现一个特点:每种物品有两种选择,放或者不放。
int n, int W; int w[MAX_N], v[MAX_N]; int process(int i, int j){ int res; if(i == n){ res = 0; } else if(j < w[i]){ res = process(i + 1, j); } else { res = max(process(i + 1, j), process(i + 1, j - w[i]) + v[i]); } return res; } void solve(){ printf("%d\n", process(0, W)); }
深搜的一个缺点就是会重复计算,所以有了备忘录式深搜,剪枝操作减少不必要的计算。
int n, int W; int w[MAX_N], v[MAX_N]; int dp[MAX_N][MAX_W + 1]; int process(int i, int j){ if(dp[i][j] >= 0) { return dp[i][j]; } int res; if(i == n){ res = 0; } else if(j < w[i]){ res = process(i + 1, j); } else { res = max(process(i + 1, j), process(i + 1, j - w[i]) + v[i]); } return dp[i][j] = res; } void solve(){ memset(dp, -1, sizeof(dp)); printf("%d\n", process(0, W)); }
还有一种深搜写法:
int process(int i, int j, int sum){ int res; if(i == n){ res = sum; } else if(j < w[i]){ res = process(i + 1, j, sum); } else { res = max(process(i + 1, j, sum), process(i + 1, j - w[i], sum + v[i]); } return res; }
这种写法不利于备忘录式搜索的实现,尽量不要用这种形式。
根据备忘录式深搜,dp[i][j]为从第i个物品开始挑选总重小于j时,总价值的最大值。
i = n; dp[n][j] = 0;
j < w[j] dp[i + 1][j]
其他 max(dp[i + 1][j], dp[i + 1][j - w[i] + v[i])
void solve(){ for (int i = n - 1; i >= 0; --i){ for(int j = 0; j <= W; ++j){ if(j < w[i]){ dp[i][j] = dp[i + 1][j]; } else { dp[i][j] = max(dp[i + 1][j], dp[i + 1][j - w[i]] + v[i]); } } } printf("%d\n", dp[0][W]); }
重新定义dp方式:dp[i + 1][j] 为 从前面i个物品中选出总重量不超过j的物品时总价值的最大值。
dp[0][j] = 0;
dp[i + 1][j] = dp[i][j] j < w[i]
max(dp[i][j], dp[i][j - w[i]] + v[i]) 其他
void solve(){ for(int i = 0; i < n; ++i){ for(int j = 0; j <= W; ++j){ if(j < w[i]){ dp[i + 1][j] = dp[i][j]; } else { dp[i + 1][j] = max(dp[i][j], dp[i][j - w[i]] + v[i]); } } } printf("%d\n", dp[n][W]); }
背包问题的基本方程式: dp[i + 1][j] = max(dp[i][j], dp[i][j - w[i]] + v[i]);
动态规划只与之前的状态有关,不会和下一个状态有联系。对于此问题的状态定义,dp[i + 1][j]为将前i件物品放入容量为j的背包,即问题的子问题。若只考虑第i件物品的策略(放或不放),那么就可以转化为一个只和前i - 1件物品相关的问题。如果不放第i件物品,那么问题就转化为“前i - 1件物品放入容量为j的背包中”,价值为dp[i - 1][j];如果放第i件物品,那么问就转化为“前i - 1件物品放入剩下的容量为j - w[i] 的背包中, 然后将第i件物品放入背包中”,此时能获得的最大价值就是dp[i][j - w[i]]再加上通过放入第i件物品获得的价值v[i],即dp[i][j - w[i]] + v[i] 。
0-1背包问题解题思路就是如此,但是在具体的代码实现中可以优化代码。
空间复杂度优化:分析dp方程:dp[i+1][a]的计算只来自dp[i][b](a >= b),那么就联想到数组复用。使用一维数组实现代码。dp[i + 1][j]由dp[i][j]和dp[i][j - w[i]]两个子问题推导得到,所以要保证在推dp[i + 1][j]时能够取用dp[i][j]和dp[i][j - w[i]]的值。也就是说,推导dp[j]时要使用上一循环的dp[j]和dp[j - w[i]],那么就必须保证在本次循环推导dp[j]时不能改写dp[j]和dp[j - w[i]]。
int dp[MAX_W + 1]; void solve(){ for(int i = 0; i < n; ++i){ for(int j = W; j >= w[i]; --j){ dp[j] = max(dp[j], dp[j - w[i]] + v[i]); } } printf("%d\n", dp[n][W]); }
这里dp[j - w[i]]对应着原来的dp[i][j - w[i]],如果将j的循环顺序颠倒,即w[i]-W,那么就成了dp[i+1][j]由dp[i][j]推导得到,与题意不符。
以上是关于动态规划背包问题的主要内容,如果未能解决你的问题,请参考以下文章