背包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 的博客 理解==
怎么求?
- 把式子抄上去,打表计算
- 发现集合里面是个全排列,可以直接二进制枚举
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]);