动态规划入门——背包问题
01背包问题之1
问题描述
有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(2n))
#include<cstdio>
#include<algorithm>
using namespace std;
int n;
int w[105], v[105];
int rec(int i, int j) //i为物品序号,j为可用重量
{
int res;
if(i == n)
res = 0;
else if(j < w[i])
res = rec(i+1, j);
else
res = max(rec(i+1, j), rec(i+1, j - w[i])+v[i]);
//rec(i+1, j)为不选该物品,rec(i+1, j - w[i])+v[i])为选择该物品
return res;
}
int main()
{
scanf("%d", &n);
int i;
for(i = 0; i < n; i++)
scanf("%d%d", &w[i], &v[i]);
int W;
scanf("%d", &W);
printf("%d\n", rec(0, W));
return 0;
}
上述算法复杂度太高,于是用了个记忆化数组,如果在递归时该内容已经在之前出现过,直接返回该值
此时复杂度为O(nW)
#include<cstdio>
#include<algorithm>
using namespace std;
int n;
int w[105], v[105];
int dp[105][105];
int rec(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 = rec(i+1, j);
else
res = max(rec(i+1, j), rec(i+1, j - w[i])+v[i]);
return dp[i][j] = res;
}
int main()
{
scanf("%d", &n);
int i;
for(i = 0; i < n; i++)
scanf("%d%d", &w[i], &v[i]);
int W;
scanf("%d", &W);
rec(0, W);
printf("%d\n", rec(0, W));
return 0;
}
这是用递归的方式写的,再来看一下用循环的方式写的01背包
复杂度同上
#include<cstdio>
#include<algorithm>
using namespace std;
int n;
int w[105], v[105];
int dp[105][105];
int rec(int W)
{
int i, j;
for(i = 0; i <n; i++)
{
for(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]);
}
}
return 0;
}
int main()
{
scanf("%d", &n);
int i;
for(i = 0; i < n; i++)
scanf("%d%d", &w[i], &v[i]);
int W;
scanf("%d", &W);
rec(W);
printf("%d\n", dp[n][W]);
return 0;
}
完全背包问题
问题描述
有n种重量和价值分别为wi和vi的物品。从这些物品中挑选总重量不超过W的物品,求出挑选物品价值总和的最大值。在这里每件物品可以挑选任意多件。
1≤n≤100
1≤wi,vi≤100
1≤W≤10000
样例
输入
n = 3
(w, v) = {(2,3),(3,4),(4,5)}
W = 7
输出
10(0号物品选1个,2号物品选2个)
样例代码
由可选任意多件可得下方代码
int rec(int W)
{
int i, j, k;
for(i = 0; i <n; i++)
{
for(j = 0; j <= W; j++)
{
for(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]);
}
}
return 0;
}
显而易见,该代码复杂度为O(nW2),并不是最优解
#include<cstdio>
#include<algorithm>
using namespace std;
int n;
int w[105], v[105];
int dp[105][105];
int rec(int W)
{
int i, j;
for(i = 0; i <n; i++)
{
for(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+1][j-w[i]] + v[i]);
//完全背包为i
}
}
return 0;
}
int main()
{
scanf("%d", &n);
int i;
for(i = 0; i < n; i++)
scanf("%d%d", &w[i], &v[i]);
int W;
scanf("%d", &W);
rec(W);
printf("%d\n", dp[n][W]);
return 0;
}
此复杂度为(nW),对比一下可以发现,与01背包问题代码略有相像
此外,01背包问题和完全背包问题也可以通过不断重复利用一个数组来实现(小心使用,易出bug)
01背包
int rec(int W)
{
int i, j;
for(i = 0; i < n; i++)
{
for(j = W; j >= w[i]; j--)
{
dp[j] = max(dp[j], dp[j-w[i]] + v[i]);
}
}
return 0;
}
完全背包
int rec(int W)
{
int i, j;
for(i = 0; i < n; i++)
{
for(j = w[i]; j <= W; j++)
{
dp[j] = max(dp[j], dp[j-w[i]] + v[i]);
}
}
return 0;
}
01背包问题之2
问题描述
有n个重量和价值分别为wi,vi的物品。从这些物品中选出总重量不超过W的物品,求所有挑选方案中价值总和的最大值。在这里,每个物品只能选一件。
限制条件:
1≤n≤100
1≤wi≤107
1≤vi≤100
1≤W≤109
样例
输入
n = 4
(w, v) = {(2,3),(1,2),(3,4),(2,2)}
W = 5
输出
7(选择0、1、3号物品)
分析
显而易见,与01背包问题之1相比,数值变大了,也就不能用O(nW)的算法了,相对于重量而言,价值的数值比较小,所以尝试一下改变dp的对象,用dp针对不同价值的最小重量。
代码如下
int dp[MAX_N+1][MAX_V+1];
int rec(int W)
{
fill(dp[0], dp[0]+MAX_N*MAX_V + 1, INF);
int i, j;
dp[0][0] = 0;
for(i = 0; i < n; i++)
{
for(j = 0; j < MAX_N*MAX_V; j++)
{
if(j < v[i])
dp[i+1][j] = dp[i][j];
else
dp[i+1][j] = min(dp[i][j], dp[i][j-v[i]] + w[i]);
}
}
int res = 0;
for(i = 0; i <= MAX_N*MAX_V; i++)
if(dp[n][i] <= W)
res = i;
printf("%d\n", res);
return 0;
}

浙公网安备 33010602011771号