【算法笔记】背包模板-算法详解

01背包

N N N 件物品和一个容量为 M M M 的背包。第 i i i 件物品所耗费的空间是 w [ i ] w[i] w[i],得到的价值是 v [ i ] v[i] v[i]。求解将哪些物品装入背包可使价值总和最大。

  • “求什么设什么”,我们用 f [ i ] [ j ] f[i][j] f[i][j] 表示前 i i i 件物品耗费空间为 j j j 可得到的最大价值。显然,答案是 f [ N ] [ M ] f[N][M] f[N][M]
  • 那么状态转移方程呢?思考一下。对于第 i i i 件物品,有取和不取两种情况。
  • 如果当前这件物品不取,那么其实前 i − 1 i-1 i1 件物品耗费的总空间就是 j j j,得到的价值不变;
  • 如果当前这件物品取,那么前 i − 1 i-1 i1 件物品耗费的总空间就是 j − w [ i ] j-w[i] jw[i],得到的价值就是 f [ i − 1 ] [ j − w [ i ] ] + v [ i ] f[i-1][j-w[i]]+v[i] f[i1][jw[i]]+v[i]
  • 所以可得转移方程: f [ i ] [ j ] = m a x ( f [ i − 1 ] [ j ] , f [ i − 1 ] [ j − w [ i ] ] + v [ i ] ) f[i][j]=max(f[i-1][j],f[i-1][j-w[i]]+v[i]) f[i][j]=max(f[i1][j]f[i1][jw[i]]+v[i])
  • 不过,二维的空间,是不是有些浪费?
  • 其实,我们可以省去 i i i 这一维,那么转移方程就变成了: f [ j ] = m a x ( f [ j ] , f [ j − w [ i ] ] + v [ i ] ) f[j]=max(f[j],f[j-w[i]]+v[i]) f[j]=max(f[j],f[jw[i]]+v[i])
  • 不过,省去了这一维,就出现了一个细节需要注意:要倒叙枚举 j j j,而不是正序枚举,因为正序枚举在枚举 f [ j ] f[j] f[j] f [ j − w [ i ] ] f[j-w[i]] f[jw[i]] 已经被覆盖过了,那么就有可能取过了第 i i i 件物品,不满足动态规划的特性:无后效性。(自己手动推一下,会更理解)
C o d e : Code: Code
    scanf("%d %d",&n,&m);
    for (int i=1;i<=n;i++) scanf("%d %d",&w[i],&v[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]]+v[i]);

完全背包

N N N 件物品和一个容量为 M M M 的背包,每件物品都有无限件。放入 第 i i i件物品耗费的空间是 w [ i ] w[i] w[i],得到的价值是 v [ i ] v[i] v[i]。求解将哪些物品放入背包可使价值总和最大;

  • 可得转移方程 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−vi]+wi) f[i][j]=max(f[i1][j],f[i][jvi]+wi)
  • 完全背包怎么压缩空间呢?其实就是 01 01 01 背包的代码,把 j j j 改成正序枚举就好了。为什么可以这样做?
  • 因为完全背包可以取无限次,枚举 f [ j ] f[j] f[j] 前第 i i i 件物品能够已经取过,符合完全背包
C o d e : Code: Code
    scanf("%d %d",&n,&m);
    for (int i=1;i<=n;i++) scanf("%d %d",&w[i],&v[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]]+v[i]);

多重背包

N N N 种物品和一个容量为 M M M 的背包。第i种物品最多有 n [ i ] n[i] n[i] 件可用,每件费用是 w [ i ] w[i] w[i],价值是 v [ i ] v[i] v[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

  • 我们可以用暴力的方法枚举,对于第i种物品有 n [ i ] + 1 n[i]+1 n[i]+1 种策略:取 0 0 0 件,取 1 1 1 件……取 n [ i ] n[i] n[i] 件, f [ i ] [ v ] = m a x ( f [ i − 1 ] [ v − k ∗ w [ i ] ] + k ∗ v [ i ] ) ( 0 < = k < = n [ i ] f[i][v]=max(f[i-1][v-k*w[i]]+k*v[i])(0<=k<=n[i] f[i][v]=max(f[i1][vkw[i]]+kv[i])(0<=k<=n[i]
  • 可是这种方法会超时 Q A Q QAQ QAQ,所以我们要用二进制优化
C o d e : Code: Code
  • 以下是一种简洁的方法:
	for (int i=1;i<=n;i++)
	 for (int j=1;n[i]>0;n[i]-=j,j=min(j*2,n[i]))
	  for (int k=v;k>=w[i]*j;k--)
	   f[k]=max(f[k],f[k-w[i]*j]+v[i]*j);

二维费用背包

对于每件物品,具有两种不同的费用;选择这件物品必须同时付出这两种代价;对于每种代价都有一个可付出的最大值(背包容量)。问怎样选择物品可以得到最大的价值。设这两种代价分别为代价 1 1 1和代价 2 2 2,第 i i i件物品所需的两种代价分别为 a [ i ] a[i] a[i] b [ i ] b[i] b[i]。两种代价可付出的最大值(两种背包容量)分别为 V V V U U U。物品的价值为 v [ i ] v[i] v[i]

  • 费用加了一维,只要状态也加一维即可;
  • 状态转移方程: f [ i ] [ j ] [ k ] = m a x ( f [ i − 1 ] [ j ] [ k ] , f [ i − 1 ] [ j − a [ i ] ] [ k − b [ i ] ] + v [ i ] ) f[i][j][k]=max(f[i-1][j][k],f[i-1][j-a[i]][k-b[i]]+v[i]) f[i][j][k]=max(f[i1][j][k],f[i1][ja[i]][kb[i]]+v[i])
  • 同样,可以压缩空间: f [ v ] [ u ] = m a x ( f [ [ j ] [ k ] , f [ j − a [ i ] ] [ k − b [ i ] ] + v [ i ] ) f[v][u]=max(f[[j][k],f[j-a[i]][k-b[i]]+v[i]) f[v][u]=max(f[[j][k],f[ja[i]][kb[i]]+v[i])
C o d e : Code: Code
  for (int i=1;i<=n;i++)
    for (int j=V;j>=a[i];j--)
      for (int k=U;k>=b[i];k--)
       f[j][k]=max(f[j][k],f[j-a[i]][k-b[i]]+v[i]);

分组背包

N N N 件物品和一个容量为 V V V 的背包。第i件物品的费用是 w [ i ] w[i] w[i],价值是 v [ i ] v[i] v[i]。这些物品被划分为若干组,每组中的物品互相冲突,最多选一件。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

  • 其实对于这种题目,我们只是多一重循环枚举组中的物品,这个问题变成了每组物品有若干种策略:是选择本组的某一件,还是一件都不选。具体看代码;
C o d e : Code: Code
	for 所有的组k
	  for (int j=V;j>=0;j--)
	    for 所有的i属于组k
	     f[j]=max{f[j],f[j-c[i]]+w[i]}
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值