01背包

GOJ 1131 为例

背包1

Time Limit: 2000/1000ms (Java/Others)

Problem Description:

有 n 个重量和价值分别为Wi,Vi的物品,现从这些物品中挑选出总量不超过 W 的物品,求所有方案中价值总和的最大值。

Input:

输入包含多组测试用例,每一例的开头为两位整数 n、W(1<=n<=10000,1<=W<=1000) ,接下来有 n 行,每一行有两位整数 Wi、Vi(1<=Wi<=10000,1<=Vi<=100)。

Output:

输出为一行,即所有方案中价值总和的最大值。

Sample Input:

3 4
1 2
2 5
3 7

Sample Output:

9

01背包也是动态规划的题目,所有的物品都只有两种状态,放入背包(1)或者不放(0)。既然是一道dp题目,那么我们先要找到子问题,由子问题得到最终问题得结果。背包总量为W,有n个物品,要求价值总和的最大值。那么当总量为W时,背包价值的最大值可以由放入的第i件物品背包的价值(W-wi)加上a[i]的和与未放入物品i的背包W比较,取两者的最大值。以此类推,可得状态转移方程为:
dp[i][j] = max(dp[i-1][j],dp[i -1][j - w[i]]+a[i])
(dp[i][j] 表示总量为j的背包,放入前i个物品所取得价值的最大值)

为加深理解可以借助表格(数据以上题为例)
ps:两张表除了物品顺序不一样外,最终的结果都是一样的。生成表的顺序从下到上,从左到右。
表一:
表一

表二:
01背包_第1张图片

举个例子(为方便叙述,用物品代号和背包总量表示蓝色格子的数据)
在表二中(3,2) = 0,代表只有物品3时,有个承重为2的背包,那么这个背包的最大价值是0,因为物品3的重量是3,背包装不了。

表二(1,4) = 9,代表有物品1,2,3时,有个承重为4的背包,这个背包的最大价值是9, 这时怎么得到的呢? 根据状态转移方程我们需要看两个值,
[2,3] + vlaue[1] = 9,与 [2,4] = 7。
[2,4]表示的是只有物品2,3两件可选时,这个背包能装入的最大价值。
[2,3]表示有一个承重为3的背包(等于当前背包[2,4]承重减去物品1的重量),当只有物品2,3件可选时,这个背包能装入的最大价值。
那么加上物品1的价值后 ,很明显该单元格填上9。

那么接下来只要模拟这个表格的生成就可以了。

#include
#include
#include
using namespace std;
int main()
{
    int n;
    int Z;
    int w[10004];
    int a[10004];
    static int dp[10004][1004];//数组太大会造成栈溢出,所以不能放在栈中,定义为静态保存在堆中或者改为全局变量
    while(scanf("%d%d",&n,&Z)!=EOF)
    {
        //初始化
        memset(w,0,sizeof(w));
        memset(a,0,sizeof(a));
        memset(dp,0,sizeof(dp));

        for(int i = 1 ; i <= n; i++)
            scanf("%d%d",&w[i],&a[i]);

        for(int i = 1 ; i <= n; i++)
        for(int j = 0; j <= Z; j++)
        {
            if(w[i] > j)
            {//装不下的另外处理
                if(j == 0)
                    dp[i][j] = 0;
                else
                    dp[i][j] = dp[i - 1][j];
            }
            else
            dp[i][j] = max(dp[i-1][j],dp[i - 1][j - w[i]] + a[i]);
        }
        printf("%d\n",dp[n][Z]);
    }
    return 0;
}

空间复杂度优化:
因为是求所有方案中价值总和的最大值,那么我们只要保存最终表的最顶行的数据即可,可以优化到O(Z) 。
先考虑上面基本思路的实现,一个主循环i=1..n,每次算出来二维数组dp[i][0..Z]的所有值。那么,用一个数组f[0..Z],保证第i次循环结束后f[z]中表示的就是我们定义的状态dp[i][j]。此外dp[i][j]是由dp[i-1][j]和dp[i-1][j-w[i]]两个子问题递推而来。在推dp[i][j]时(也即在第i次主循环中推f[j]时)要能够得到dp[i-1][j]和dp[i-1][j-w[i]]的值,要求在每次主循环中我们以j=Z..0的顺序推f[j],这样才能保证推f[j]时f[j-w[i]]保存的是状态dp[i-1][j-w[i]]的值。如果将z的循环顺序从上面的逆序改成顺序的话,那么则成了dp[i][j]由dp[i][j-w[i]]推知,与本题意不符(另外的完全背包问题),故用一维数组解决的方法也很重要。

生成一维数组dp[j]表格(变化方向: 从左到右,从上到下 ):
01背包_第2张图片

同样,只要能填上面表格,那么代码也就容易写了。

#include
#include
using namespace std;
int main()
{
    int n;
    int Z;
    int w[10004];
    int a[10004];
    int dp[1004];
    while(scanf("%d%d",&n,&Z)!=EOF)
    {
        memset(w,0,sizeof(w));
        memset(a,0,sizeof(a));
        memset(dp,0,sizeof(dp));

        for(int i = 1 ; i <= n; i++)
            scanf("%d%d",&w[i],&a[i]);

        for(int i = 1; i <= n; i++)
        for(int j = Z; j >= w[i]; j--)
            dp[j] = max(dp[j - w[i]] + a[i], dp[j]);
    printf("%d\n",dp[Z]);
    }
    return 0;
}

上交结果表明优化空间复杂度,在数据很大的时候,运行时间的差距是相当可观的。

你可能感兴趣的:(dp,01背包,算法,动态规划,动态规划)