P01:01背包问题
有N种物品和一个容量为V的背包。第i种物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包的容量,且价值总和最大。每种物品仅有一件。
P02:完全背包问题
有N种物品和一个容量为V的背包。第i种物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包的容量,且价值总和最大。每种物品都有无限件。
P03:多重背包问题
有N种物品和一个容量为V的背包。第i种物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包的容量,且价值总和最大。每种物品最多有n[i]件。
P01:
定义子状态dp[i][j],用来表示前i种物品(包括第i种物品)放入容量为j的背包中时,获得的最大价值,可以称它为第i个状态。那么,当那什么结束之后,就能得到dp[N][V],它就是第N个状态,表示的意思就是将前N种物品放入容量为V的背包中时,获得的最大价值,即是我们要求解的答案。
那么,第i个状态是怎么来的呢?第N个状态又是如何那什么得来的?很简单,第i个状态肯定来自于第i-1个状态,由第N-1个状态就可得到第N个状态。当处于第i个状态时,第i种物品有两种选择:放入背包、不放入背包。因为每种物品有它自己的容量和价值,而且背包也有容量限制。所以,不是放得越多得到的价值总和最大。而是有选择的,从N种物品中选择一部分物品放入背包中,才能使得在不超过背包容量的情况下,价值总和最大。
1、处于第i个状态时,第i种物品放入背包。此时,第i-1个状态是dp[i-1][j-c[i]],表示将前i-1个物品放入容量为j-c[i]的背包中时,获得的最大价值。因为第i种物品选择放入背包中,所以,第i-1个状态时,背包的容量为j-c[i]。那么此时dp[i][j]的值是多少呢?dp[i-1][j-c[i]]表示的是上一个状态的最大价值,再加上第i种物品的价值,就是dp[i][j]的值,dp[i][j]=dp[i-1][j-c[i]]+w[i].
2、处于第i个状态时,第i种物品不放入背包。此时第i-1个状态是dp[i-1][j],表示将前i-1个物品放入容量为j的背包中时,获得的最大价值。因为第i种物品选择不放入背包中,所以不占体积。此时,dp[i][j]=dp[i-1][j]。
综上:dp[i][j]=max(dp[i-1][j],dp[i-1][j-c[i]]+w[i]);
例题:HDU 2602
/**
**author :Or_me **
╭︿︿︿╮
{/ A C /}
( (OO) )
︶︶︶
** **
** 题**
** 2014 年 月 日**
**/
#include <cmath>
#include <queue>
#include <stack>
#include <vector>
#include <cstdio>
#include <string>
#include <cctype>
#include <climits>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
using namespace std;
int c[1100],w[1100];
long long dp[1100][1100];
int main()
{
int T;
scanf("%d",&T);
while (T--)
{
int n,v;
scanf("%d%d",&n,&v);
for (int i=1;i<=n;i++)
scanf("%d",&w[i]);//价值
for (int i=1;i<=n;i++)
scanf("%d",&c[i]);//花费
memset(dp,0,sizeof(dp));
for (int i=1;i<=n;i++)
for (int j=v;j>=0;j--)//这个题的数据很强,容量和体积都可能为0
{
if (j>=c[i])
dp[i][j]=max(dp[i-1][j],dp[i-1][j-c[i]]+w[i]);
else
dp[i][j]=dp[i-1][j];//保留了前一个状态
printf("dp[%d][%d]~~~%d~~~\n",i,j,dp[i][j]);
}
printf("%I64d\n",dp[n][v]);
}
return 0;
}
测试数据:
Input
1
5 0
2 4 1 5 1
0 0 1 0 0
Output
12
上面使用的方法,时间复杂度为O(N*V),空间复杂度也为O(N*V)。很明显,时间复杂度已经不能优化了,但空间复杂度还可以优化到O(V)。从O(N*V)优化到O(V),从这句话就能很明显的看出,定义子状态的dp数组从二维变到了一维。
先来复习下01背包基本的思路,首先有一个主要的for循环for(int i=1;i<=n;i++)来表示对每个背包的选择,然后再是一个for循环for(int j=v;j>=0;j--)来计算对于第i种物品的所有值,dp[i][0...v]。dp[i][j]是由两个子状态dp[i-1][j]、dp[i-1][j-c[i]]递推而来,没有这两个子状态,就不能得出dp[i][j],当使用一维的数组f[j]时,在第二个for循环中,以j=v...0的顺序推f[j],就能保证推f[j]时f[f-c[i]]中保存的是状态dp[i-1][j-c[i]]的值。此时,状态转移方程dp[i][j]=max(dp[i-1][j],dp[i-1][j-c[i]]+w[i])就相当于f[j]=max(f[j],f[j-c[i]]+w[i])。在计算f[j]之前,f[j]里保存的是dp[i-1][j]的值,f[j-c[i]]保存的是dp[i-1][j-c[i]]的值,因为是逆序枚举的。
这个就是滚动数组的应用。要理解如何从二维优化到一维,就要先理解好滚动数组,有一个计算斐波拉切数列第n项的小例子:
/**
**author :Or_me **
╭︿︿︿╮
{/ A C /}
( (OO) )
︶︶︶
** **
** 题**
** 2014 年 月 日**
**/
#include <cmath>
#include <queue>
#include <stack>
#include <vector>
#include <cstdio>
#include <string>
#include <cctype>
#include <climits>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
using namespace std;
int main()
{
int n;
while (~scanf("%d",&n))
{
int a[4];
a[0]=0;
a[1]=1;
a[2]=1;
a[3]=2;
if (n<=3)
printf("%d\n",a[n]);
else
{
for (int i=1;i<=n-3;i++)
{
a[1]=a[2];
a[2]=a[3];
a[3]=a[1]+a[2];
}
printf("%d\n",a[3]);
}
}
return 0;
}