动态规划之背包问题

动态规划重点

1.状态表示

2.动态转移方程

1.01背包

一个旅行者有一个最多能用 m 公斤的背包,现在有 n 件物品,它们的重量分别是w_1,w_2,w_3,.....,w_n它们的价值分别为 c_1,c_2,c_3,.....,c_n​。若每种物品只有一件求旅行者能获得最大总价值。

若用dfs or 位运算 爆搜,那是2^n, n≥11就爆了

令一数组 f

状态标识:

        f[i][j]:前 i 件物品用 j 公斤的背包 

动态转移方程:

        分为选i,与不选i

        选 i

               那我要留w[i]空间,只能用(j-w[i]),我可以得c[i] 所以是f[i-1][j-w[i]]+c[i]

        不选 i 

                那就是f[i-1][j]

        那结果是 max(f[i-1][j],f[i-1][j-w[i]]+c[i])

所以代码如下:

for(int i=1;i<=n;++i)
	for(int j=0;j<=m;++j){//枚举 
		f[i][j]=f[i-1][j];//原先是这样 
		if(j>w[i])//选w[i]你必须能装得下 
			f[i][j]=max(f[i][j],f[i-1][j-w[i]]+c[i]);
		//求最大值 
	}

时间复杂度 O(n^2)

但空间复杂度:O(n^2)

那能不能空间优化一下:

首先,要明白要降 i 这一位

否则 (j-w[i]) ……就不能实现

f[j] : 用 j 公斤能装下的最大价值

那如果就直接把 i 位删了

for(int i=1;i<=n;++i)
	for(int j=w[i];j<=m;++j)
		f[j]=max(f[j],f[j-w[i]]+c[i]);

那会出现问题

f[j-w[i]] 就被这一次(i)更新过 ,我要的是(i-1) 

那我咋样不更新那些我要用的点?

因为 j >(j-w[i]) 所以 f[ (j,m] ] 不会影响 f[j]

所以我把 j 倒过来枚举就ok 了

for(int i=1;i<=n;++i)
	for(int j=m;j>=w[i];--j)
		f[j]=max(f[j],f[j-w[i]]+c[i]);

稍微解释一下,f[i][j]=f[i-1][j] 这行降维就是 “废话” (f[j]=f[j])

所以直接简写成这样

那看一道最简单的题

就是输入

m,n

w[i] , c[i]

然后01背包就 ok 了

代码就不加注释了

#include <bits/stdc++.h>
using namespace std;
int w[207],c[207],f[1007];
int main(){
	int m,n;
	cin>>m>>n;
	for(int i=1;i<=n;++i)cin>>w[i]>>c[i];
	for(int i=1;i<=n;++i)
		for(int j=m;j>=w[i];--j)
			f[j]=max(f[j],f[j-w[i]]+c[i]);
	cout<<f[m];
	return 0;
}

因为 w[i],c[i] 是“一次性的”

把w[],c[] 优化掉 就是

#include <bits/stdc++.h>
using namespace std;
int w,c,f[1007];
int main(){
	int m,n;
	cin>>m>>n;
	for(int i=1;i<=n;++i){
        cin>>w>>c;
		for(int j=m;j>=w;--j)
			f[j]=max(f[j],f[j-w]+c);
	}
	cout<<f[m];
	return 0;
}

2.完全背包

状态表示:

        f[i][j]:前 i 件物品用 j 公斤的背包 

分为两种方法去理解

1.定义理解法

        分为选i,与不选i了

        又选 i

               那我要留w[i]空间,所以是只能用(j-w[i]),又能再选 i ,我可以得 c[i] 所以是f[i][j-w[i]]+c[i]

        不选 i 

                那就是f[i-1][j]

        那结果是 max(f[i-1][j],f[i][j-w[i]]+c[i])

2.规律理解法

我们设 i 件选了 k 次

那要留 k * w[i] 的位置,剩 (j-k*w[i]) 价值为 k * c[i],就是f[i-1][j-k*w[i]]+k*c[i],那

        f[i][j]=max{f[i-1][j],f[i-1][j-w[i]]+c[i] ,f[i-1][j-2*w[i]]+2*w[i]……,f[i-1][j-k*w[i]]+k*c[i]}

 f[i][j-w[i]]=max{           f[i-1][j-w[i]]        ,f[i-1][j-w[i]]+c[i]        ……,f[i-1][j-k*w[i]]+(k-1)*c[i]}

后面部分就是少了个c[i],其他都一样

所以就是f[i][j]=max{f[i-1][j],f[i][j-w[i]]+c[i]}

代码:

for(int i=1;i<=n;++i)
	for(int j=0;j<=m;++j){//枚举 
		f[i][j]=f[i-1][j];//原先是这样 
		if(j>w[i])//选w[i]你必须能装得下 
			f[i][j]=max(f[i][j],f[i][j-w[i]]+c[i]);
		//求最大值 
	}

是否可以降维?也是可以的!
这个不需其他变化

因为这样枚举f[j-w[i]]正好是被(i)更新过了

for(int i=1;i<=n;++i)
	for(int j=0;j<=m;++j)
		f[j]=max(f[j],f[j-w[i]]+c[i]);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值