2026年5月5日预计 11 分钟阅读OI

背包DP

zbl2012
zbl2012博主 & 创作者

背包DP是 DP 中最基础的一种,比较好理解的,接下来介绍几种背包DP。

首先要了解背包DP解决的问题,就是类似于求给定一个可取价值和一些物品数量和单个价值,在各种约束下可以达到的最值。

01背包

属于最基础的了,但是其他背包基本都要建立在它身上。

01背包就是有 nn 物品,每个物品有它的价值 viv_i 和重量 wiw_i,你可以选总容量为 WW 的物品,但是每件物品只能选一件,求最大价值。

我们设 fi,jf_{i,j} 表示选前 ii 个物品,最大容量为 jj 的最大价值。 这个数组的转移显然是 fi,j=max(fi1,j,fi1,jwi+vi)f_{i,j}=\max(f_{i-1,j},f_{i-1,j-w_i}+v_i) 满足 wijw_i\le j。 这个转移了,求最大的两个数 fi1,jf_{i-1,j}fi1,jwi+vif_{i-1,j-w_i}+v_i 分别是当前位置不取,取上一个位的值,和当前位置取,所以价值要减去 wiw_i,这里省略使用表格来理解的过程。 答案显然是 fn,Wf_{n,W}

这个转移是时间复杂度是 O(nW)O(nW),是优秀的,但是空间复杂度 O(nW)O(nW) 对比起来就是 shit。 考虑滚动数组优化空间。

fjf_j 表示前 jj 的物品的最大价值。 转移其实和二维的一样,是 fj=max(fj,fjwi+vi)f_j=\max(f_j,f_{j-w_i}+v_i) 且满足 wijw_i\le j。 在此处,可以省略掉第一位的原因就是因为第 ii 个数据只受 i1i-1 的影响,所以可以滚动优化。 但是注意滚动优化的时候,第二层循环要从大至小遍历,不然的话有一些数据会多取几次。 因为是一维且从大到小,只有从 WW 循环到 wiw_i 即可。 当然滚动数组也可以用两个数组来进行交换实现。 答案显然是 fWf_W

空间复杂度 O(W)O(W)

注意点 1.注意如果用二维的,第二次循环是有从小到大,而滚动数组的是从大到小 2.注意滚动是答案是 fWf_W,不要写成 fnf_n。 3.用二维数组的时候不要直接从 wiw_i 遍历的 WW,因为它是从小到大循环的。 4.ff 数组的大小要开 maxW\max W

code

cpp
#include<bits/stdc++.h>
using namespace std;
const int N=105;
int w[N],v[N];
int W,n;
int dp[20010];
int main(){
	cin>>W>>n;
	for(int i=1;i<=n;i++){
		cin>>w[i]>>v[i];
	}
	for(int i=1;i<=n;i++){
		for(int j=W;j>=w[i];j--){
            dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
		}
	}
	cout<<dp[W];
	return 0;
}

完全背包

完全背包就是有 nn 物品,每个物品有它的价值 viv_i 和重量 wiw_i,你可以选总容量为 WW 的物品,每件物品可以拿任意次,求最大价值。

完全背包和01背包的区别就在于可以取多次。

这里只讲述滚动优化的,朴素的以后在补。

fjf_j 表示前 jj 的物品的最大价值。 转移:fj=max(fj,fjwi+vi)f_j=\max(f_j,f_{j-w_i}+v_i) 且满足 wijw_i\le j。 这时你会发现:这个东西不是和01背包的长得完全一样吗? 确实是。 但是,不同点就在于怎么遍历取更新这一个 ff 数组。 在01背包中,我们说了,01背包的第二层循环要倒序遍历,因为正序遍历有一些数据会多计算,而这正好符合了完全背包的做法,所以只需要将循环从 wiw_iWW 即可。

时间复杂度 O(nW)O(nW),空间复杂度 O(W)O(W)注意点 注意要正序遍历。 其他同01背包。

code

cpp
#include<bits/stdc++.h>
using namespace std;
const int N=105;
int w[N],v[N];
int W,n;
int dp[20010];
int main(){
	cin>>W>>n;
	for(int i=1;i<=n;i++){
		cin>>w[i]>>v[i];
	}
	for(int i=1;i<=n;i++){
		for(int j=w[i];j<=W;j++){
			dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
		}
	}
	cout<<dp[W];
	return 0;
}

多重背包

多重背包就是有 nn 物品,每个物品有价值 viv_i 和重量 wiw_i,还有数量 sis_i,你可以选总容量为 WW 的物品,每件物品最多可以拿 sis_i 个,求最大价值。

先考虑朴素做法。

发现这一个问题可以拆成多个01背包来解决,就是把第 ii 种物品拆分成 cic_i 种只有一个的物品的01背包去做。

时间复杂度貌似是枚举每一个重量的和拆01背包的 O(Vci)O(V\sum c_i)

太慢了,考虑优化。

使用二进制分组优化,因为每一个正整数都可以表示为一个二进制,我们可以反推过来,用 logn\log n 个二进制表示从 00cic_i 的任何数,将 cic_i 是用二进制拆分,得到的结果去算贡献替换掉 wiw_iviv_i 中的值,就可以做到很快的速度处理多重背包了。

时间复杂度 O(nmlogV)O(nm\log V)

还有一种使用单调队列优化的空间 O(nm)O(nm) 的做法,等我学了再写。

注意点 1.注意二进制分组的剩余的值要特殊处理。

code

朴素做法:

cpp
#include<bits/stdc++.h>
using namespace std;
const int N=10100;
int w[N],v[N],s[N];
int W,n;
int dp[N];
int main(){
	cin>>n>>W;
	for(int i=1;i<=n;i++){
		cin>>w[i]>>v[i]>>s[i];
	}
	for(int k=1;k<=n;k++){
		for(int i=1;i<=s[k];i++){
			for(int j=W;j>=w[k];j--){
				dp[j]=max(dp[j],dp[j-w[k]]+v[k]);
			}
		}
	}
	cout<<dp[W];
	return 0;
}

二进制优化:

cpp
#include<bits/stdc++.h>
using namespace std;
const int N=100100;
int w[N],v[N],s[N];
int W,n;
int dp[N];
int cnt;
int vi,wi,si;
int main(){
	cin>>n>>W;
	for(int i=1;i<=n;i++){
		cin>>wi>>vi>>si;
		int k=1;
		while(k<=si){
			cnt++;
			w[cnt]=wi*k;
			v[cnt]=vi*k;
			si-=k;
			k*=2;
		}
		if(si){
			cnt++;
			w[cnt]=wi*si;
			v[cnt]=vi*si;
		}
	}
	for(int i=1;i<=cnt;i++){
		for(int j=W;j>=w[i];j--){
			dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
		}
	}
	cout<<dp[W];
	return 0;
}

分组背包

分组背包就是有 nn 组物品,每组物品有 sis_i 个,第 sis_i 里的第 jj 个物品有价值 vi,jv_{i,j} 和重量 wi,jw_{i,j},每组至多可以拿一个,求在可以拿 WW 个的情况下的最大价值。

发现本题可以直接把每组拆成01背包去做,每一次在里面多跑一个 sis_i 组的东西,然后直接模版就行了,

时间复杂度貌似是 O(nVmaxsi)O(nV\max s_i),空间复杂度貌似是 O(V+nmaxsi)O(V+n\max s_i)

注意点 1.注意跑01背包时的转换模板中的 viv_iwiw_i

code

cpp
#include<bits/stdc++.h>
using namespace std;
const int N=110;
int v[N][N],w[N][N],s[N];
int dp[N];
int n,W;
int main(){
	cin>>n>>W;
	for(int i=1;i<=n;i++){
		cin>>s[i];
		for(int j=1;j<=s[i];j++){
			cin>>v[i][j]>>w[i][j];
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=W;j>=0;j--){
			for(int k=1;k<=s[i];k++){
				if(j>=v[i][k]){
					dp[j]=max(dp[j],dp[j-v[i][k]]+w[i][k]);
				}
			}
		}
	}
	cout<<dp[W];
	return 0;
}

文章留言区

已有 0 条精彩探讨

正在拼命加载留言中...

发表您的见解

※ 提倡客观理性讨论。留言需要经过安全核查,请勿注入恶意链接。
上一篇文章AC自动机下一篇文章 拓扑排序