01背包——优化详解!!!(滚动数组、一维优化)

背包九讲

背包九讲原文——作者崔添翼(dd_engi)

文章由以上两篇文章总结而来,相信能让读者理解透彻,不用再翻那么多博客反复学习 新人勿喷!

前提:一般情况下,优化用滚动数组就可以了,只是一维优化在完全背包问题中很有用。

滚动数组

原来空间复杂度:O(NV)   滚动数组优化后空间复杂度:O(2*N)

01背包
先考虑01背包基本思路如何实现

dp[i,j]:1~i个物品组成重量<=j的背包时,能获取的最大价值

状态转移方程:dp[i][j]=max(f[i−1][j],f[i−1][j−c[i]]+w[i])

可以看出,dp[i,j]只依赖与自己和i-1,而滚动数组就是把i给变成2,反复用0、1来进行dp,上一轮的值还是可以找到。

先给出代码:

for (int i = 1; i < N; i++) { // F[i,j]: 1~i个物品所能产生的最大价值
	int ci = i % 2, i1 = ci ^ 1;
	for (int j = 0; j <= V; j++) {
        dp[ci][j]=dp[i1][j];
		if (j >= w[i])
		   dp[ci][j] = max(dp[ci][j], F[i1][j - w[i]] + v[i]);
	}
}

ci:目前行,i1:上一行     原理:1异或1=0 0异或1=1

i1永远跟ci不同,且正好是ci的上一行。

一维优化

原来空间复杂度:O(NV)   一维优化后空间复杂度:O(N)

01背包
先考虑01背包基本思路如何实现

dp[i,j]:1~i个物品组成重量<=j的背包时,能获取的最大价值

状态转移方程:dp[i][j]=max(f[i−1][j],f[i−1][j−c[i]]+w[i])

这个方程转化成一维,也要依赖   f[i−1][j] 和 f[i−1][j−c[i]]  这两个前项。那能否保证在推f[i][j]时,能够得到 f[i−1][j] 和 f[i−1][j−c[i]]的值呢?其实只需要把原来j从1~V改成从V~1就行了。

为啥呢?

状态转移方程:dp[j]=max(dp[j],dp[j-w[i]]+v[i])

这一轮循环为i,而我们要用上一轮(即i-1循环)的状态,就必须逆序,因为f(j)要用f(j-w[i])来推,而  j 一定是大于等于 j - w[i]的,也就是说,j一定在j-w[i]的上方,如下图 ,1~V就无法用上一轮更新的值了,而V~1就行,举个例子:

有两件物品:① 价格:2  重量: 1  ②价格:3 重量:1 

第①轮,不管j是从1~V,还是从V~1,状态为:f(1)=2,f(2)=2

第②轮,如果是1~V,那状态为:f(1)=3,f(2)=6

明显不正确

                如果是V~1,那状态为f(2)=5,f(1)=3.

为了深入理解,我画个图表示更新情况:

 一句话总结,逆序才能让当前值能用上一轮(也就是i-1轮)推出的值,因为这样才能让上一轮推出的值在当前值的上方(如上图)

代码如下:

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

拓展应用
Two Sets II - CSES 1093 - Virtual Judge (vjudge.net)https://vjudge.net/problem/CSES-1093

这是一道分割等和子集的经典题型,读者可以自己尝试做一下,也可以继续往下看。

这道题其实就是一个01背包,只是变成了方案数,限定了总和

大家可以自行理解

且关于最后2逆元的推算,如下:

a*2^{-1} mod P x=2^{-1} \rightarrow x*2\equiv 1 mod P \frac{P+1}{2}*2=P+1 mod P

代码如下:

Source code - Virtual Judge (vjudge.net)https://vjudge.net/solution/43960514/LjLzcZ2Aj82rOymSaqP2

//01背包 
#include<bits/stdc++.h>
using namespace std;
const int mod=1e9+7;
int n;
using ll=long long;
ll dp[505][63000];
int main(){
	cin>>n;
	int sum=n*(n+1)/2;
	if(sum%2==1){
		cout<<0;
		return 0;
	}
	dp[0][0]=1;
	sum/=2;
	for(int i=1;i<=n;i++){
		for(int j=0;j<=sum;j++){
			dp[i][j]=dp[i-1][j];
			if(i<=j) (dp[i][j]+=dp[i-1][j-i])%=mod;
		}
	}
	cout<<dp[n][sum]*(mod+1)/2%mod;
	return 0;
}

之后我会慢慢发布多重背包、哈希、等等的算法详解。尽情期待。

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
通过空间优化版本的0-1背包问题解法,可以利用一维数组进行优化。这是因为在0-1背包问题中,每个物品只能选择放入背包一次或不放入,所以在计算第i个物品放入容量为j的背包的最大价值时,只需要考虑前i-1个物品放入容量为j的背包的最大价值和前i-1个物品放入容量为j-vol[i的背包的最大价值加上第i个物品的价值,取两者的较大值即可。通过这种方式,可以将二维数组转化为一维数组来实现空间优化。 具体的实现方法如下: 1. 创建一个一维数组dp,长度为背包的容量V+1,用于存储每个容量下的最大价值。 2. 遍历每个物品,从最后一个物品开始倒序遍历。 3. 对于当前遍历到的物品i,在容量为j的背包中,判断是否能够放入该物品: - 若当前容量j小于物品i的体积vol[i,则无法放入,最大价值保持不变,即dp[j = dp[j。 - 若当前容量j大于等于物品i的体积vol[i,则可以选择放入该物品或不放入该物品,取两者的较大值作为最大价值,即dp[j = max(dp[j], dp[j-vol[i]] + val[i])。 4. 完成遍历后,最终的最大价值即为dp[V。 这样,通过一维数组的空间优化,可以在0-1背包问题中节省空间,并且得到相同的最优解。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [leetcode 背包类问题](https://blog.csdn.net/u014034683/article/details/114481074)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* [0-1背包问题的一维数组优化解析](https://blog.csdn.net/hnjzsyjyj/article/details/126071689)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值