第五章 动态规划(一)

一、背包问题

n n n件物品和一个容量为 m m m的背包。物品 i i i的体积是 v i v_i vi,价值是 w i w_i wi。求解将哪些物品装入背包可使价值总和最大

1.01背包问题

01背包问题特点:每种物品仅有一件

状态表示:定义 f ( i , j ) f(i,j) f(i,j)表示只从前 i i i个物品中选择,总体积不超过 j j j的最大价值,最终答案为 f ( n , m ) f(n,m) f(n,m)

状态转移: f ( i , j ) f(i,j) f(i,j)可以分为不选物品 i i i和选物品 i i i两种情况

状态转移方程:
f ( i , j ) = m a x ( f ( i − 1 , j ) , f ( i − 1 , j − v i ) + w i ) f(i,j)=max(f(i-1,j),f(i-1,j-v_i)+w_i) f(i,j)=max(f(i1,j),f(i1,jvi)+wi)

int f[N][N];
for(int i=1;i<=n;i++){
	for(int j=1;j<=m;j++){
		f[i][j]=f[i-1][j];
		if(j>=v[i]) f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i]);
	}
}

优化:从二维变成一维

int f[N];
//j从大到小遍历,保证f[j]在f[j-v[i]]改变前改变,从而使当前的f[j-v[i]]表示i-1时的状态
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]);

2.完全背包问题

完全背包问题特点:每种物品有无数件

思路:类比01背包

状态表示:定义 f ( i , j ) f(i,j) f(i,j)表示只从前 i i i个物品中选择,总体积不超过 j j j的最大价值

状态转移: f ( i , j ) f(i,j) f(i,j)可以分为不选物品 i i i和选 1 1 1 ~ ⌊ j v i ⌋ \lfloor\frac{j}{v_i}\rfloor vij个物品 i i i两种情况

等价变形: f ( i , j ) f(i,j) f(i,j)可以分为选 0 0 0 ~ ⌊ j v i ⌋ \lfloor\frac{j}{v_i}\rfloor vij个物品 i i i的情况

状态转移方程:
f ( i , j ) = m a x ( f ( i , j ) , f ( i − 1 , j − k v i ) + k w i ) ( k ∈ [ 0 , ⌊ j v i ⌋ ] , k ∈ Z ) f(i,j)=max(f(i,j),f(i-1,j-kv_i)+kw_i)\quad(k\in [0,\lfloor\frac{j}{v_i}\rfloor],k\in Z) f(i,j)=max(f(i,j),f(i1,jkvi)+kwi)(k[0,vij],kZ)

int f[N][N];
for(int i=1;i<=n;i++)
	for(int j=0;j<=m;j++)
		for(int k=0;k*v[i]<=j;k++)
			f[i][j]=max(f[i][j],f[i-1][j-k*v[i]]+k*w[i]);

优化:对于 f ( i , j ) f(i,j) f(i,j) f ( i , j − v i ) f(i,j-v_i) f(i,jvi)而言:

f ( i , j ) = m a x ( f ( i − 1 , j ) , f ( i − 1 , j − v i ) + w i , f ( i − 1 , j − 2 v i ) + 2 w i , ⋯   ) f(i,j)=max(f(i-1,j),f(i-1,j-v_i)+w_i,f(i-1,j-2v_i)+2w_i,\cdots) f(i,j)=max(f(i1,j),f(i1,jvi)+wi,f(i1,j2vi)+2wi,)

f ( i , j − v i ) = m a x ( f ( i − 1 , j − v i ) , f ( i − 1 , j − 2 v i ) + w i , f ( i − 1 , j − 3 v i ) + 2 w i , ⋯   ) f(i,j-v_i)=max(f(i-1,j-v_i),f(i-1,j-2v_i)+w_i,f(i-1,j-3v_i)+2w_i,\cdots) f(i,jvi)=max(f(i1,jvi),f(i1,j2vi)+wi,f(i1,j3vi)+2wi,)

我们惊奇的发现: f ( i , j − v i ) f(i,j-v_i) f(i,jvi)的表达式与 f ( i , j ) f(i,j) f(i,j)的表达式除第一项以外只差 w i w_i wi

于是将状态转移方程改写为:
f ( i , j ) = m a x ( f ( i − 1 , j ) , f ( i , j − v i ) + w i ) f(i,j)=max(f(i-1,j),f(i,j-v_i)+w_i) f(i,j)=max(f(i1,j),f(i,jvi)+wi)

int f[N][N];
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]) f[i][j]=max(f[i][j],f[i][j-v[i]]+w[i]);
	}

再优化:从二维变成一维

int f[N];
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]);

3.多重背包问题

多重背包问题特点:每种物品有件数限制

额外定义物品 i i i s i s_i si

状态表示:定义 f ( i , j ) f(i,j) f(i,j)表示只从前 i i i个物品中选择,总体积不超过 j j j的最大价值

状态转移: f ( i , j ) f(i,j) f(i,j)可以分为选 0 0 0 ~ m i n ( s i , ⌊ j v i ⌋ ) min(s_i,\lfloor\frac{j}{v_i}\rfloor) min(si,vij)个物品 i i i的情况

状态转移方程:
f ( i , j ) = m a x ( f ( i , j ) , f ( i − 1 , j − k v i ) + k w i ) ( k ∈ [ 0 , m i n ( s i , ⌊ j v i ⌋ ) ] , k ∈ Z ) f(i,j)=max(f(i,j),f(i-1,j-kv_i)+kw_i)\quad(k\in [0,min(s_i,\lfloor\frac{j}{v_i}\rfloor)],k\in Z) f(i,j)=max(f(i,j),f(i1,jkvi)+kwi)(k[0,min(si,vij)],kZ)

时间复杂度: O ( n m s ) O(nms) O(nms)

for(int i=1;i<=n;i++)
	for(int j=0;j<=m;j++)
		for(int k=0;k<=s[i]&&k*v[i]<=j;k++)
			f[i][j]=max(f[i][j],f[i-1][j-k*v[i]]+k*w[i]);

优化:转化为01背包问题

假设物品 i i i 1000 1000 1000件,则可以选择的数量有 0 , 1 , 2 , ⋯   , 1000 0,1,2,\cdots,1000 0,1,2,,1000,我们将物品按 1 , 2 , 4 , ⋯   , 256 , 489 ( 1,2,4,\cdots,256,489( 1,2,4,,256,489(此处为 1000 − 511 ) 1000-511) 1000511)件进行“打包”,则可将问题转化为“打包”后新 log ⁡ n \log n logn个物品的01背包问题,时间复杂度 O ( n m s ) O(nms) O(nms)变为 O ( n m log ⁡ s ) O(nm\log s) O(nmlogs)

//这里注意 N 至少要开到 n * logs (基佬紫警告
int v[N],w[N];
int f[N];
int n,m,cnt;
//边读边打包
for(int i=1;i<=n;i++){
	int a,b,s;
	scanf("%d%d%d",&a,&b,&s);
	//打包存储,cnt记录打包后物品总数量 
	int k=1;
	while(k<=s){
		cnt++;
		v[cnt]=a*k;
		w[cnt]=b*k;
		s-=k;
		k*=2;
	}
	//剩余不足 2 的更高次幂个物品额外打包 
	if(s>0){
		cnt++;
		v[cnt]=a*s;
		w[cnt]=b*s;
	}
} 
//物品个数为cnt,背包容量为m的01背包问题模板
for(int i=1;i<=cnt;i++)
	for(int j=m;j>=v[i];j--)
		f[j]=max(f[j],f[j-v[i]]+w[i]);

4.分组背包问题

分组背包问题特点:物品分为多组,每组内有若干个每组只能选一个

状态表示:定义 f ( i , j ) f(i,j) f(i,j)表示只从 i i i物品中选择,总体积不超过 j j j的最大价值

状态转移: f ( i , j ) f(i,j) f(i,j)可以分为不从第 i i i组物品中选和从第 i i i组物品中选第 k k k个物品两种情况

状态转移方程:
f ( i , j ) = m a x ( f ( i − 1 , j ) , f ( i − 1 , j − v ( i , k ) ) + w ( i , k ) ) f(i,j)=max(f(i-1,j),f(i-1,j-v_{(i,k)})+w_{(i,k)}) f(i,j)=max(f(i1,j),f(i1,jv(i,k))+w(i,k))

优化:从二维变成一维(类比01背包, j j j从大到小遍历)

int n,m;//n组物品,容量为m
int v[N][N],w[N][N],s[N];//s[i]表示第i组物品的件数
int f[N];
for(int i=1;i<=n;i++)
	for(int j=m;j>=0;j--)
		for(int k=1;k<=s[i];k++)
			if(v[i][k]<=j)
				f[j]=max(f[j],f[j-v[i][k]]+w[i][k]);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值