Loading

背包dp进阶

背包dp

01背包

有若干物品,每个物品只有一个,要么选要么不选,每个物品有 一个容积和价值。

solution:

\(f_{i,V}\) 表示当前考虑到第 \(i\) 个物品时,总容积为 V 的最大 价值。每次转移的时候枚举前面的容积和当前物品选或者不选

总复杂度 \(O(nV)\)

如果价值较小时,可以考虑将价值放入状态,记录容积的最小值。 滚动数组优化

完全背包

有若干种物品,每种物品有无限个,每个物品有一个容积和一个价值,在不超过背包大小的情况下放入价值尽可能大的物品

solution:

\(01\) 背包一个样,因为 \(01\) 背包避免一个背包选多次,所以就倒序枚举,那完全背包就正序枚举就好了

多重背包

有若干种物品,每种物品有 \(a_i\) 个,每个物品有一个容积和一个价值。在不超过背包大小的情况下放入价值尽可能大的物品

solution:

二进制优化:

因为任何数都可以表示成某些 \(2^n\) 加和来表示,所以把 \(a_i\) 进行二进制拆分,将 \(2^k\) 个物品看做一个整体,按照 \(01\) 背包做

for(int i = 1; i <= n; i++){
   int num = min(a[i], V / w[i]); 
   for(int k = 1; num > 0; k <<= 1){
   	   if(k > num) k = num;
   	   num -= k;
   	   for(int j = V; j >= w[i] * k; j--)
   	   	f[j] = max(f[j], f[j - w[i] * k] + v[i] * k);   
   }  
}

单调队列优化

[HNOI2007] 梦幻岛宝珠

solution

01背包板子题目,但花费和价值都特别的大,但花费都能表示成 \(a*2^b\) 的形式 \(a \leq 10,b\leq 30\)

从题目的特殊条件考虑,我们可以考虑对所有 \(b\) 相同的物品进行背包,再进行合并

这样把每个物品花费的 \(2^b\) 提出来,花费就可以看做 \(a\) ,价值不变,直接 \(01\) 背包就好了

这样就可以求出在各种容量下,只放 \(b\) 相同的物品的最大收益

可以理解为给你一个 \(b\) 现在你已经知道在任何容量下只放 \(a*2^b\) 这种物品的最大收益

很显然,背包里面不可能只放 \(b\) 相同的物品,那么怎么把 \(b\) 不同的背包进行合并?

令上方求得每类背包为 \(f[i][j]\) ,表示对于 \(b\)\(i\) 的物品背包,容量为 \(j\) 的最大价值

再设一个数组 \(g[i][j]\) 表示使用 \(b\)\(0 \sim i\) 的物品进行背包,\(b\) 的价值所占空间为 \(j\) 时,剩余物品剩余物品(即 \(b\)\(1\sim i-1\))所占空间为 \(m\&((1<<i)-1)\) 的最大价值。

考虑转移:

\(g[i][j]=max(g[i][j],f[i][j−k]+g[i−1][min(10*n,k*2+(V>>(i−1))\&1)]\)

  • \(10*n\) 因为 \(a\leq 10\) 所以每一位上的最大容量为 \(10 * n\)
  • \(k*2\) 上一位拿出来剩下的空间拿给下一位需要 *2(进制向右移)
  • \((V>>(i−1))\&1)\) 判断第 \(i\) 位是不是 \(1\) ,也就是再把这一位上的空间加上

最后答案为 \(g[V的位数] [1]\)

消失之物

solution:

卷积问题:分别预处理一个前缀背包和一个后缀背包

\(f[i][j]:\) 表示前 \(i\) 个物品填满容量为 \(j\) 的背包的方案数

\(g[i][j]\) : 表示后 \(i\) 个物品填满容量为 \(j\) 的背包的方案数

\(ans = \sum_{j = 0}^S = f[i -1][j] * g[n - i][S-j]\)

复杂度

\(O(n^2*m)\)

考虑背包转移式子

如果 \(j \geq w[i]\) \(f[j] = f[j - w[i]]\)

每个 \(j\) 都是由 \(f[j - w[i]]\) 倒序转移过来的,现在去除一个物品那就正序除去这个物品就好了

 memcpy(g, f, sizeof f);
   	  for(int j = 1; j <= m; j++){
   	  	  if(j >= w[i])g[j] = (g[j] - g[j - w[i]] + 10) % 10;
		  printf("%d", g[j]);
	  }

P1450[HAOI2008]硬币购物

solution

暴力直接每天一次多重背包,时间复杂度 \(O(dsn)\)一周都跑不完

考虑如果没有硬币数量的限制,那就是个完全背包了

换一种想法:所有在限制之内的方案数 = 所有方案数 - 不合法的方案数

一种神奇的思想:

考虑对一枚硬币限制

假设一个货币一次购买的使用上限为 \(d_i\), 我们现在让它使用 \(d_i + 1\) 次,用剩下的其他硬币对 \(f[s - c[i]*(d_i + 1)]\) 的空间跑完全背包,那么跑出来的方案就都是不合法的,那么合法的方案数就是总的方案数减去跑出来的方案数,这就是对一枚硬币的限制情况

对四枚硬币限制:

直接用 \(f[S]\) 减去分别对四种硬币的限制情况??

显然不对,当对一个硬币起限制情况的时候,减去了这个硬币超过范围的情况,但也可能减去对其他硬币超过范围的情况

所以应该为容斥:

根据Y_B_Y 的博客 理解==

\[ans = f[s]−card(A)−card(B)−card(C)−card(D)\\ ~~\\ +card(A∩B)+card(A∩C)+card(A∩D)\\ ~~\\ +card(B∩C)+card(B∩D)+card(C∩D)\\~~\\ -card(A∩B∩C)-card(A∩B∩D)\\~~\\-card(A∩C∩D)-card(B∩C∩D)\\~~\\+card(A∩B∩C∩D) \]

怎么求?

  • 把式子抄上去,打表计算
  • 发现集合里面是个全排列,可以直接二进制枚举

BZOJ4976宝石镶嵌

\(n\) 个物品,每个物品价值依次为 \(w_1, w_2, ..., w_n\) 背包容量为 \(n -k\) ,价值为背包中的所有物品价值按位或的结果,求怎样使得价值最大

\(n, w_i ≤ 10^5 , k ≤ 100\)

solution

考虑如果 \(n − k \geq 17\),那么我们对于每一位都选一个对应位为 \(1\) 的宝石即可。

只需要考虑 \(n − k < 17\) 的情况,即 \(n < 117\)

直接背包,\(f[i]\) 表示或为 \(i\) 的时候所需要的最小的数

时间复杂度:\(O(2^{17}*k)\)

for (int i = 1;i <= n; i++)
    for (int j = (1 << 17);j>=0;j++)
        f[i + 1][j | w[i]] = min([f[i+1][j | w[i]], f[i][j]+1)
for (int i = (1 << 17);i > 0; i--)
    if (f[n + 1][i] <= n - k) printf("%d",f[n + 1][i]);

posted @ 2021-05-04 22:17  Dita  阅读(107)  评论(5编辑  收藏  举报