动态规划之基础背包——01背包

DP!!!

背包就是DP,只不过是一种类型罢了

首先,what is beibao(背包)?

顾名思义,背包问题就是求怎么样才能让自己背包里的物品最值钱。


 

让我们通过一个故事来进一步了解背包问题吧!

终于放假了,作为996的你,终于获得了一个七天的假期。

有假期,当然要出去玩啦!

你现在有一个背包。背包就是用来装东西的嘛!所以,你开始往背包里边塞东西了……

看着一床的衣服、洗簌用品、拖鞋,当然还有电脑。想带的东西太多了,你没法全部都带上QAQ!那怎么办呢?

作为OIER的你,打开了你的电脑,想通过编程解决这个问题。

每一件物品在你心中都有一个实用性值,你想用你有限的背包空间带上实用性最高的那些物品;

物品的实用性值和大小如下:

衣服:100 8

洗漱用品:80 3

拖鞋:50 5

小说:90 7

作业:10 6

电脑:100 10

好了,你现在开始思考了:

首先,你定义了两个个数组来存你的数据:val[7]是每一件物品的实用性值;w[7]是每一件物品的大小。

你的背包大小为t=10;

你尝试了各种办法:暴力,枚举,贪心……

最终你使用“暴力出奇迹”大法得出了答案;但是,作为有强迫症的你,不甘心用暴力,所以,你想到了DP!

先定义一个二维的数组dp[i][j];

dp[i][j]表示装进前i个物品、背包容量为j时的最大实用性值(1~i-1的物品已经装入)(这个数组在一开始就要全部赋值为0!!)


 

以下为思考过程:

你现在拿着衣服,也就是第一件物品val[1]=100;w[1]=8,如果j<8,那么,很明显,你就无法将衣服塞进背包里,所以dp[1][j]=0,也就是说dp[1][1]~dp[1][7]=0;

如果j>=8的话,那你就可以塞进背包了,所以dp[1][j]=100;(这显然是最优解)

下一件,你拿着洗漱用品,val[2]=80;w[2]=3;这事,和上面那个一样,如果j<3,那么你就塞不下,dp[2][j]=0,也就是说dp[2][1]~dp[2][2]=0;

如果j>=3那么你就往里面塞。这时dp[2][j]=80;但是!这并不是最优,dp[2][j]=100,因为你空间只有10,你不可能把衣服和洗漱用品同时塞进背包,你只可能塞一个。很明显,塞衣服的实用性值比塞洗漱用品的实用性值要高,所以你应该塞进衣服。这么一算你有两种塞入方式:1.塞衣服;2.塞洗漱用品(因为空间只有10<w[1]+w[2],所以没有第三种——都塞)

这样,我们来分别列出两种方式:

当j=10的时候

1.dp[2][j]=dp[1][j-w[2]]+val[2]  ==>  dp[2][10]=dp[1][10-3]+80(dp[1][10-3]=dp[1][7]=0,因为7<8,所以无法放进衣服)==> dp[2][10]=80;

“dp[1][j-w[i]]+val[2]”是因为你现在选择塞入洗漱用品,那么你的空间就应该减少w[2]=3,然后你的值dp[2][10]就应该加上val[2]=80

2.dp[2][j]=dp[1][j] ==> dp[2][10]=dp[1][10] (因为第一种没法塞进衣服,所以这种情况就是塞衣服而不塞洗漱用品)==> dp[2][10]=100;

这种方案就是不塞洗漱用品,只塞衣服,那么现在的值dp[2][10]就应该等于塞衣服时dp[1][10]的值

这么一列,显而易见地,第二种方案是更优的,所以我们选第二种


 

那么,通过上面的分析,我们不难推导出状态转移方程:dp[i][j]=max( dp[ i-1 ][ j-w[i] ] + val[i] , dp[i-1][j] );

DP只要推出了状态转移方程,那就容易了!

但是要注意了,如果j<w[i]的时候怎么办?

如果眼下这个塞不进去,那么就不塞了,那现在的dp[i][j]就是dp[i-1][j]

话不多说,直接上代码:

//这是洛谷P1048采药的代码
//友善的我呈上网址:https://www.luogu.org/problemnew/show/P1048

#include<bits/stdc++.h> using namespace std; int t,m;//t是背包容量、m是物品的数量 int val[110],w[110]; int dp[110][1010];//第一个参数表示第i个物品;第二个参数表示背包容量为j int main() { cin>>t>>m; for(int i=1;i<=m;i++) { cin>>w[i]>>val[i]; } for(int i=1;i<=m;i++) { for(int j=t;j>=0;j--)//这里要循环到0,注意倒序 { if(j>=w[i])//判断这个物品能否塞入 { dp[i][j]=max(dp[i-1][j-w[i]]+val[i],dp[i-1][j]); } else//如果不能塞就不塞,那么现在的实用性值还是等于上一个的实用性值 { dp[i][j]=dp[i-1][j]; } } } cout<<dp[m][t]<<endl;//全部塞完之后,整个二维数组的最后一个值就是最终的答案QwQ!!! return 0; }

看完之后,有的同学可能会问,这么高的空间复杂度,不会MLE吗?

你们放心,会!

所以,我们需要优化空间复杂度。

既然参数j已经无法优化了,那我们就优化参数i!!!

依旧是那道题:

#include<bits/stdc++.h>

using namespace std;

int t,m;
int val[110],w[110];
int dp[1100];

int main()
{
    cin>>t>>m;
    for(int i=1;i<=m;i++)
    {
        cin>>w[i]>>val[i];
    }
    for(int i=1;i<=m;i++)
    {
        for(int j=t;j>=0;j--)
        {
            if(dp[j]<=dp[j-w[i]]+val[i] && j-w[i]>=0)
            {
                dp[j]=dp[j-w[i]]+val[i];
            }
        }
    }
    cout<<dp[t]<<endl;
    return 0;
}
/*
上面标红的部分可以继续优化(请先理解上面的代码再看优化)

  for(int j=t;j>=w[i];j--)
  {
    dp[j]=max(dp[j-w[i]]+val[i],dp[j]);
  }

  改动部分以用AC的颜色标出

*/

温馨提示:这种一维优化利用了滚动数组。滚动数组的缺点就是会抹掉前面的数据。因为这道题的每一个阶段都是独立的,和前面的阶段没啥关系,所以可以优化。在做题的时候要小心QAQ!

大家明白了吗?如果不明白,可以再看一遍我的文章(某人不厚道地笑了)或者去OJ上面刷一些题目。当然,欢迎留言问我问题!

posted @ 2019-07-22 11:32  kingderman  阅读(203)  评论(0编辑  收藏  举报