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上面刷一些题目。当然,欢迎留言问我问题!