动态规划初步
动态规划算法通常基于一个递推公式及一个或多个初始状态。 当前子问题的解将由上一次子问题的解推出。使用动态规划来解题只需要多项式时间复杂度, 因此它比回溯法、暴力法等要快许多。以背包问题为例
01背包问题
有n个重量和价值分别为wi和vi的物品,从这些物品中挑选出总价值不超过W的物品,求这些方案中价值最大的一种。这类问题最易想到的就是朴素递归进行穷竭搜索,但是递归过程无论是算法复杂度还是空间占有率都比较高。我们可以将这个问题转化为递推的问题,建立二维数组大盘dp[i][j],dp[i][j]表示从0到i这i+1个物品的总重量不超过j的物品的总价值最大者,则递推代码可以这样描述
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]);
}
}
cout << dp[n][W] << endl; }
上面利用动态数组的算法都是利用的二维数组dp,其实我们会发现,利用递推关系时,每次需要改变值的数组都是在同一行的,所以我们可以不断的覆盖前一行,只利用一维数组便可以求解。
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]);
}
}
cout << dp[W];
}
由01背包问题可以衍生出完全背包问题,完全背包问题中,物品无限多件,只要背包没满,就可以一直添加,这类问题可以想之前一样考虑二维数组储存,但同时也可以借鉴之前的思路将代码优化到一维,与01背包不同的是此时需要从前往后循环遍历
#include<bits/stdc++.h>
using namespace std;
int w[10],v[10],dp[105];
int main()
{
int m,n;//m为 容量 n为物品种类
cin >> m >> n;
for(int i = 1;i <= n;i ++)
cin >> w[i] >> v[i];
for(int i = 1;i <= n;i ++){
for(int j = 1;j <= m;j ++)
if(j >= w[i])
dp[j] = max(dp[j],dp[j-w[i]] + v[i]);
}
cout << dp[m];
return 0;
}
还有一种基本背包问题多重背包,是每件物品都有给定的数量,如果按照安全背包的思想,每件物品的数量为num[i],即1<=k<=num[i],可以将问题转化为01背包问题
public static int bag4(int W, int[] w, int[] v, int[] num) {
int n = w.length - 1;// 第一个值,不算
int[] f = new int[W + 1];
for (int i = 1; i <= n; i++)
for (int j = W; j >= w[i]; j--)
for (int k = 0; k <= num[i]; k++) {
if (j - k * w[i] >= 0 && f[j - k * w[i]] + k * v[i] > f[j])
f[j] = f[j - k * w[i]] + k * v[i];
}
return f[W]; // 最优解
}