课程总结——背包

这篇博客详细介绍了各种类型的背包问题,包括0/1背包、完全背包、多重背包、分组背包和多维费用背包。通过二维DP和优化一维DP的方法解决这些问题,并探讨了滚动数组的优化技巧。此外,还讲解了直接拆分法和二进制拆分法在多重背包中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

0/1背包

模型:给定 N N N物品,其中第 i i i个物品的体积为 V i V_i Vi,价值为 W i W_i Wi。有一容积为 M M M的背包,要求选择一些物品放入背包,使得物品总体积不超过 M M M的前题下,物品的价值总和最大。

二维DP
  • F [ i , j ] F[i,j] F[i,j]表示从前 i i i个物品中选出了总体积为 j j j的物品,物品最大价值和。
  • F [ i , j ] = m a x { F [ i − 1 , j ] F [ i − 1 , j − V i ] + W i } F[i,j]=max\begin{Bmatrix} F[i-1,j] \\ F[i-1,j-V_i]+W_i \end{Bmatrix} F[i,j]=max{F[i1,j]F[i1,jVi]+Wi}
    第一种是不选第 i i i个物品,直接继承 i − 1 i-1 i1.
    第二种是选第 i i i个物品,将上一次取物品的价值加上这个物品的价值,且 j ≥ V i j\ge V_i jVi.
  • 初值:F[0][0]=0,其余为负无穷(防止最大价值本身为0,无法判断是否取物品,能判断F[n][m]是否恰好装满)。
  • 目标: m a x { F [ N , j ] } , 0 ≤ j ≤ M max\{ F[N,j]\},0\le j\le M max{F[N,j]},0jM.
memset(f,0xcf,sizeof(f));//-INF,相当于将符号位改为1;
	f[0][0]=0; 
	for(int i=1;i<=n;i++){
		for(int j=0;j<=m;j++) f[i][j]=f[i-1][j];//从0开始考虑了不取的情况
		for(int j=v[i];j<=m;j++) f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i]);
		//满足了0<=j<=M
	}
滚动数组

通过分析代码,发现外层转移只与 i i i i − 1 i-1 i1有关系,所以可以用滚动数组优化空间,降低空间开销。

int f[2][m+1];//外层只开两位,分别是0和1;
memset(f,0xcf,sizeof(f));//-INF,相当于将符号位改为1;
	f[0][0]=0; 
	for(int i=1;i<=n;i++){
		for(int j=0;j<=m;j++) f[i&1][j]=f[(i-1)&1][j];//位运算的奇偶判断,偶0奇1
		for(int j=v[i];j<=m;j++) f[i&1][j]=max(f[i&1][j],f[(i-1)&1][j-v[i]]+w[i]);//
	}
优化一维DP

我们还能发现:因为 f [ i ] [ j ] = f [ i − 1 ] [ j ] f[i][j]=f[i-1][j] f[i][j]=f[i1][j],只做了拷贝的工作,所以f数组的第一维没有存在的必要。

所以代码可以化简为这样

int f[m+1];
memset(f,0xcf,sizeof(f));
	f[0]=0; 
	for(int i=1;i<=n;i++){
		for(int j=v[i];j<=m;j++) f[j]=max(f[j],f[j-v[i]]+w[i]);//v[i]~m正序
	}
  • 这个代码是错误的,因为:假设 f [ j ] f[j] f[j] f [ j − v [ i ] ] f[j-v[i]] f[jv[i]]更新,当循环到 f [ j + v [ i ] ] f[j+v[i]] f[j+v[i]]时又会被更新一次,相当于同一个物品被选了多次,这显然违背了0/1背包.
int f[m+1];
memset(f,0xcf,sizeof(f));
	f[0]=0; 
	for(int i=1;i<=n;i++){
		for(int j=m;j>=v[i];j++) f[j]=max(f[j],f[j-v[i]]+w[i]);//m~v[i]倒序
	}
  • 这样倒序循环就解决了问题,假设 f [ j ] f[j] f[j] f [ j − v [ i ] ] f[j-v[i]] f[jv[i]]更新,当循环到了 f [ j − v [ i ] ] f[j-v[i]] f[jv[i]]会被 f [ j − 2 v [ i ] ] f[j-2v[i]] f[j2v[i]]更新,保证只选一次。

完全背包

模型:给定 N N N物品,其中第 i i i种物品的体积为 V i V_i Vi,价值为 W i W_i Wi,并且有无数个。有一容积为 M M M的背包,要求选择若干个物品放入背包,使得物品总体积不超过 M M M的前题下,物品的价值总和最大。

二维DP
  • F [ i , j ] = m a x { F [ i − 1 , j ] F [ i , j − V i ] + W i } F[i,j]=max\begin{Bmatrix} F[i-1,j] \\ F[i,j-V_i]+W_i \end{Bmatrix} F[i,j]=max{F[i1,j]F[i,jVi]+Wi}

    选的情况由 i − 1 i-1 i1变成 i i i,就可以重复选取一个物品。

优化一维DP
  • 在刚才的正序代码中我们发现可以重复选取一个物品,这不正是我们要的完全背包,所以;

    int f[m+1];
    memset(f,0xcf,sizeof(f));
    	f[0]=0; 
    	for(int i=1;i<=n;i++){
    		for(int j=v[i];j<=m;j++) f[j]=max(f[j],f[j-v[i]]+w[i]);//v[i]~m正序
    	}
    

多重背包

模型:给定 N N N物品,其中第 i i i种物品的体积为 V i V_i Vi,价值为 W i W_i Wi,并且有 C i C_i Ci个。有一容积为 M M M的背包,要求选择若干个物品放入背包,使得物品总体积不超过 M M M的前题下,物品的价值总和最大。

  • 思路:拆分,将 C i C_i Ci拆成不同物品再做0/1背包
直接拆分法
  • 比较简单,但时间复杂度高,为 O ( M ∗ ∑ i = 1 n C i ) O(M*\sum_{i=1}^{n}C_i) O(Mi=1nCi)

    int f[m+1];
    memset(f,0xcf,sizeof(f));
    	f[0]=0; 
    	for(int i=1;i<=n;i++){
            for(int k=1;k<=c[i];k++)
    			for(int j=m;j>=v[i];j++) f[j]=max(f[j],f[j-v[i]]+w[i]);//m~v[i]倒序
    	}
    
二进制拆分法

分组背包

模型:给定 N N N物品,其中第 i i i组有 C i C_i Ci个物品。第 i i i组的第 j j j个物品的体积为 V i j V_ij Vij,价值为 W i j W_ij Wij。有一容积为 M M M的背包,要求选择若干个物品放入背包,每组至多选一个,使得物品总体积不超过 M M M的前题下,物品的价值总和最大。

多维费用背包

模型:给定 N N N物品,其中第 i i i个物品的体积为 V i V_i Vi,重量为 G i G_i Gi,y值为 W i W_i Wi。有一容积为 M M M,载重 H H H的背包,要求选择一些物品放入背包,使得物品总体积不超过 M M M,总重量不超过 H H H的前题下,物品的价值总和最大。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值