动态规划—背包问题
动态规划方法代表了这一类问题(最优解结构or子问题最优性)的一般解法,是设计方法或者策略,不是具体算法。
本质是递推,核心是找到状态转移的方式,写出dp方程
形式:
—记忆性递归
—递推
动态规划(DP)在程序设计竞赛中经常被选作题材,有许多经典的DP问题,背包问题就是其中最典型的一种
01背包问题
有n个重量和价值分别为wi,vi的物品。从这些物品中跳出总重量不超过W的物品,求所有挑选方案中价值总和的最大值。
!限制条件
1<=n<=100
1<=wi,vi<=100
1<=W<=10000
样例
输入
n=4
(w,v)={(2.3),(1,2),(3,4),(2,2)}
W=5
输出
7(选择0、1、3号物品)
题目分析
首先这道题目是可以用深搜来解决的,但是深搜的话每一层的搜索都需要两次分支,最坏就需要O(2^n)的时间,当n比较大的时候就没办法解决了。并且最重要的是在深搜中会出现数据重复计算的情况,极大浪费了计算时间。所以我们会想到是否可以把第一次计算的结果记录下来,在第二次遇到同样的数据时直接调用。
这就是记忆化搜索的思想,动态规划也是利用了这一点。
例如dp[1][3],此时剩余背包容量为3,如果要这个(1,2)这个物品,那么剩余容量变为2,此时看dp[0][2]=3,所以价值v=3+2;如果不要这个物品,此时dp[1][3]=dp[0][3],两者取其max,得到5。
依律可构造出完整dp表,然后依据dp表写出dp式。
代码(C++)
#include<iostream>
#include<algorithm>
using namespace std;
int dp[101][101]; //DP数组
void solve(int n,int w[], int v[],int W)
{
//构建dp表
for (int i = 0; i <= W; i++) //初始化第一行
{
if (i < w[0])
{
dp[0][i] = 0;
}
else
{
dp[0][i] = v[0];
}
cout << dp[0][i] << " ";
}
cout << endl;
for (int i = 1; i < n; i++) //其他行
{
for (int j = 0; j <= W; j++)
{
if (j < w[i])
{
dp[i][j] = dp[i - 1][j];
cout << dp[i][j] << " ";
}
else
{
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - w[i]] + v[i]);
cout << dp[i][j] << " ";
}
}
cout << endl;
}
cout << endl << "物品价值总和的最大值为:" << dp[n - 1][W];
}
int main()
{
int n,W;
cout << "请输入物品个数:" << endl;
cin >> n;
int *w = (int *)malloc(sizeof(int)*n);
int *v = (int *)malloc(sizeof(int)*n);
cout << "请依次输入物品的重量和价值:" << endl;
for (int i = 0; i < n; i++)
{
cin >> w[i] >> v[i];
}
cout << "请输入物品总和限制的重量:" << endl;
cin >> W;
cout << "dp表如下:" << endl;
solve(n, w, v, W);
}
!!注意
1.为了更加明了,代码的输入输出与样例略有差异。
2.构造dp表最重要的的一点是对DP数组进行初始化,初始化DP数组第一行时一定要注意。
3.dp[i-1][j-w[i]]+v[i]
这里的代码表示的是要这个物品的价值+背包容量减去该物品重量后的最大价值(这个最大价值就是依据前面递归推导的)
完全背包问题
有n种重量和价值分别为wi,vi的物品。从这些物品中跳出总重量不超过W的物品,求所有挑选方案中价值总和的最大值。在这里,每种物品可以挑选任意多件。
!限制条件
1<=n<=100
1<=wi,vi<=100
1<=W<=10000
样例
输入
n=3
(w,v)={(3.4),(4,5),(2,3)}
W=7
输出
10(0号物品选择1个,2号物品选择2个)
题目分析
物品可以选多件,那么在01背包问题的基础上就需要多一层循环
for(int k=0;k*w[i]<=j;k++)
dp[i+1][j]=max(dp[i+1][j],dp[i][j-k*w[i]]+k+v[i])
但是多加的这层循环就导致算法的复杂度为O(nW^2),我们可以发现这个三重循环下是存在多余的计算的,在dp[i+1][j]的计算选择k(k>=1)个的情况,与在dp[i+1][j-w[i]]的计算中选择k-1个的情况是相同的,所以dp[i+1][j]的递推中k>=1部分的计算已经在dp[i+1][j-w[i]]的计算中完成了。
所以我们可以不需要k的循环了。
代码(C++)
#include<iostream>
#include<algorithm>
using namespace std;
int dp[101][101]; //DP数组
void solve(int n, int w[], int v[], int W)
{
//构建dp表
for (int i = 0; i <= W; i++) //初始化第一行
{
if (i >= w[0])
{
dp[0][i] = (i / w[0])*v[0];
}
else
{
dp[0][i] = 0;
}
cout << dp[0][i] << " ";
}
cout << endl;
for (int i = 1; i < n; 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][j - w[i]] + v[i]);
}
cout << dp[i][j] << " ";
}
cout << endl;
}
cout << endl << "物品价值总和的最大值为:"<< dp[n-1][W];
}
int main()
{
int n, W;
cout << "请输入物品个数:" << endl;
cin >> n;
int *w = (int *)malloc(sizeof(int)*n);
int *v = (int *)malloc(sizeof(int)*n);
cout << "请依次输入物品的重量和价值:" << endl;
for (int i = 0; i < n; i++)
{
cin >> w[i] >> v[i];
}
cout << "请输入物品总和限制的重量:" << endl;
cin >> W;
cout << "dp表如下:" << endl;
solve(n, w, v, W);
}
!!注意
1.在dp表构造其他行时,要注意对j<w[i]
的判断,否则会导致max(dp[i - 1][j], dp[i][j - w[i]] + v[i])
出错。
2.上述两个问题的代码都输出了dp表,可以对照思考。