前言
一开始看了3次视频还是一头雾水,看了前面大佬们的题解也还是一脸懵逼,最后十分感谢一篇 博客 的讲解出多重背包的二进制优化的三个要点,最后去看视频。
我才完全解决我最重要的疑惑:二进制优化,它为什么正确,为什么合理,凭什么可以这样分??
题目
样例
4 5
1 2 3
2 4 1
3 4 3
4 5 2
数据范围
0 < N ≤ 1000
0 < V ≤ 2000
0 < vi,wi,si ≤ 2000
疑惑1:为什么最后一项会是$f[i-1,j-(S+1)v]+Sw$??
在完全背包中,通过两个状态转移方程:
$f[i , j ] = max( f[i-1,j] , f[i-1,j-v]+w , f[i-1,j-2v]+2w , f[i-1,j-3v]+3w, .....)$
$f[i , j-v]= max( f[i-1,j-v] , f[i-1,j-2v] + w, f[i-1,j-2v]+2w , .....)$
通过上述比较,可以得到 $f[ i ][ j ] = max(f[ i - 1 ][ j ],f[ i ][ j - v ] + w)$。
再来看下多重背包,
$f[i , j ]$ = $max( f[i-1,j] ,f[i-1,j-v]+w ,f[i-1,j-2v]+2w ,..... f[i-1,j-Sv]+Sw, )$
$f[i , j-v]$= $max( f[i-1,j-v] ,f[i-1,j-2v]+w, ..... f[i-1,j-Sv]+(S-1)w$,$f[i-1,j-(S+1)v]+Sw)$
怎么比完全背包方程比较就多出了一项?
其实,一般从实际含义出发来考虑即可,这里是在分析$f[i,j-w]$这个状态的表达式,首先这个状态的含义是 从前i个物品中选,且总体积不超过j-w的最大价值, 我们现在最多只能选s个物品,因此如果我们选s个第i个物品,那么体积上就要减去 $s * v$,价值上就要加上$s * w$,那更新到状态中去就是 $f[i - 1, j - v - s * v] + s * w $
那为什么完全背包不会有最后一项?
完全背包由于对每种物品没有选择个数的限制,所以只要体积够用就可以一直选,没有最后一项。
疑惑2:为什么不能用像完全背包一样去优化?
疑惑3:二进制优化,它为什么正确,为什么合理,凭什么可以这样分??
我们首先确认三点:
(1)我们知道转化成01背包的基本思路就是:判断每件物品我是取了你好呢还是不取你好。
(2)我们知道任意一个实数可以由二进制数来表示,也就是$2^0~2^k$其中一项或几项的和。
(3)这里多重背包问的就是每件物品取多少件可以获得最大价值。
分析:
-
如果直接遍历转化为01背包问题,是每次都拿一个来问,取了好还是不取好。那么根据数据范围,这样的时间复杂度是$O(n^3)$,也就是$10^9$,这样是毫无疑问是会TLE的。
-
假如10个取7个好,那么在实际的遍历过程中在第7个以后经过状态转移方程其实已经是选择“不取”好了。现在,用二进制思想将其分堆,分成$k+1$个分别有$2^k$个的堆,然后拿这一堆一堆去问,我是取了好呢,还是不取好呢,经过dp选择之后,结果和拿一个一个来问的结果是完全一样的,因为dp选择的是最优结果,而根据第二点任意一个实数都可以用二进制来表示,如果最终选出来10个取7个是最优的在分堆的选择过程中分成了$2^0=1,2^1=2,2^2=4,10 - 7 = 3$ 这四堆,然后去问四次,也就是拿去走dp状态转移方程,走的结果是第一堆1个,取了比不取好,第二堆2个,取了比不取好,第三堆四个,取了比不取好,第四堆8个,取了还不如不取,最后依旧是取了$1+2+4=7$个。
Tip:参考博客
如果仍然不是很能理解的话,取这样一个例子:要求在一堆苹果选出n个苹果。我们传统的思维是一个一个地去选,选够n个苹果就停止。这样选择的次数就是n次
二进制优化思维就是:现在给出一堆苹果和10个箱子,选出n个苹果。将这一堆苹果分别按照$1,2,4,8,16,.....512$分到10个箱子里,那么由于任何一个数字$x \in [0,1023]$ (第11个箱子才能取到1024,评论区有讨论这个)都可以从这10个箱子里的苹果数量表示出来,但是这样选择的次数就是 $≤10次$ 。
比如:
- 如果要拿$1001$次苹果,传统就是要拿$1001$次;二进制的思维,就是拿7个箱子就行(分别是装有$512、256、128、64、32、8、1$个苹果的这7个箱子),这样一来,1001次操作就变成7次操作就行了。
这样利用二进制优化,时间复杂度就从 $O(n^3)$ 降到$O(n^2logS)$,从$4*10^9$降到了$2*10^7$。
代码
#include<iostream>
using namespace std;
const int N = 12010, M = 2010;
int n, m;
int v[N], w[N]; //逐一枚举最大是N*logS
int f[M]; // 体积<M
int main()
{
cin >> n >> m;
int cnt = 0; //分组的组别
for(int i = 1;i <= n;i ++)
{
int a,b,s;
cin >> a >> b >> s;
int k = 1; // 组别里面的个数
while(k<=s)
{
cnt ++ ; //组别先增加
v[cnt] = a * k ; //整体体积
w[cnt] = b * k; // 整体价值
s -= k; // s要减小
k *= 2; // 组别里的个数增加
}
//剩余的一组
if(s>0)
{
cnt ++ ;
v[cnt] = a*s;
w[cnt] = b*s;
}
}
n = cnt ; //枚举次数正式由个数变成组别数
//01背包一维优化
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]);
cout << f[m] << endl;
return 0;
}
后言
把自己菜哭了。。。。
学完算法考研去了,2年没登录社区,现在自己写得东西自己都看不懂了。。。实属离大谱了。。。
太真实了。。
hhhh
hhh
人间真实hh
haha,绝了
大哥,你的苹果让我迈入了计算机的大门🙏🙏🙏谢谢你我的大哥
有点难绷住🤣
23333333太真实了
蚌埠住了大哥哈哈
谢谢你的苹果
难绷
哈哈,那博主上岸了吗?
上了嘻嘻
哈哈,哪里的
感谢大佬的苹果
hhhh
还不懂背包问题的朋友们可以看看这篇博客,感觉很详细:https://blog.csdn.net/raelum/article/details/128996521
大佬太经典了
大佬我大一,现在也走上算法之路了,向大佬看齐!!
我现在也大一,跟着y总学算法
大佬我也是,本人现在大三下,今年六月份的时候打完所有的算法比赛之后就要专心闭关考研了,不知道何时才能再在acwing上刷算法了......
哈哈哈哈这么离谱吗
祝你上岸!!
谢谢~借你吉言!
大佬都参加了啥算法比赛啊
蓝桥杯 传智杯 CCPC之类的,然后就是在赛氪上时不时找找比赛打,我也算不上大佬哈哈哈,和真正的大佬们差远了qwq
⭐大佬的苹果,还有箱子
y总没有总结背包问题模板,需要模板的可以去看看这个博客: https://blog.csdn.net/m0_61654975/article/details/141210427
同样一句话把数字变苹果就能理解了hhh
真实。。。
大佬,“完全背包由于对每种物品没有选择个数的限制,所以只要体积够用就可以一直选”。您的这一句可谓是醍醐灌顶,精彩!
把自己菜哭了,这句话戳中了我的泪点
俺也一样
+1+1
完全背包只有背包体积够不够用,而多重背包既要考虑背包体积,又要考虑背包体积够了但物品数量不够,对吧兄弟们。
这是精髓
《这里写错了》
怎么比完全背包方程比较就多出了一项?
其实,一般从实际含义出发来考虑即可,这里是在分析f[i,j−w]
这个状态的表达式,首先这个状态的含义是 从前i个物品中选,且总体积不超过j-w的最大价值, 我们现在最多只能选s个物品,因此如果我们选s个第i个物品,那么体积上就要减去 s∗v,价值上就要加上s∗w,那更新到状态中去就是 f[i−1,j−v−s∗v]+s∗w
应该是f[i,j-v] 这个表达式的最大值 写成f[i,j-w]了
确实写错了
# 我爱
苹果nb
hhhc我也是
谢谢你的苹果
这个苹果是真的牛逼
如果还不明白的童鞋可以看看这篇click here,感觉讲的很详细
按照二进制的思想将1001个苹果分箱,其实得到的分别是装有490、256、128、64、32、16、8、4、2、1个苹果的这些箱子,不会得到一个装有512个苹果的箱子的。
楼主可能只是全按照2的次数分的话理解一些
个人理解
为什么不能用像完全背包一样去优化?
完全背包:只考虑体积,数量趋向无穷
多重背包:考虑体积,数量
完全背包数量是趋向无限的,但是背包体积是有限的,所以在背包体积有限的情况下,对两个趋向无穷个数的式子按有限体积切分,式子个数一定是一样的
图例(-代表式子个数(无穷个数) |代表体积最大值)
式子1(f[i−1,j−v]+w,f[i−1,j−2v]+2w,f[i−1,j−3v]+3w,....) -----------------|--------------
式子2(f[i−1,j−v],f[i−1,j−2v]+w,f[i−1,j−2v]+2w,.....) -----------------|---------------
(不管在哪切,因为-无限个,两个式子-个数一定相等)
多重背包数量是有限的,f[i,j−v]比f[i,j]中的f[i−1,j−v]+w,f[i−1,j−2v]+2w,f[i−1,j−3v]+3w,.....一定多一个式子,但是体积也是有限的,所有切分的时候,是可能式子个数相等,也有可能差一个式子的,所以不一定对
图例(对的)(-代表式子个数(无穷个数) |代表体积最大值)
式子1(f[i−1,j−v]+w,f[i−1,j−2v]+2w,f[i−1,j−3v]+3w,....) —|
式子2(f[i−1,j−v],f[i−1,j−2v]+w,f[i−1,j−2v]+2w,.....) —|-
(二式因为错位相等比一式子多一个-,-不是无限的,切分的时候可能出现式子不相等或者相等的情况)
图例(错的)(-代表式子个数(无穷个数) |代表体积最大值)
式子1(f[i−1,j−v]+w,f[i−1,j−2v]+2w,f[i−1,j−3v]+3w,....) — |
式子2(f[i−1,j−v],f[i−1,j−2v]+w,f[i−1,j−2v]+2w,.....) ----|
这个解释非常大到位,完全背包虽然数量是无限的,但体积是有限的,分多少个取决于体积。多重背包,体积、个数都是有限的,所以分子式的个数,取决于两个里小的那个。
没错,多重背包要从体积限制和个数限制两个方面去考虑,而两种限制条件下项数差一项,而且即使仅考虑数量限制,多出的那一项取max并不能代入j情况下的情况。
N = 12010 这个是怎么算出来的呀
2000 * log 2000
是 1000log2000 ,log2000上取整是11,就可以得到100011=11000,+10避免越界 = 11010
我直呼 nbnbnbnbnb!居然懂了!把这些凑出来的数用 01 背包自己去选 wakao!好强🤩
强啊
为什么可以一堆一起问?如果最优解是取一堆里的一部分而剩下的不要呢?
cnt是每行输入都会有的吧,那既然这样的话cnt不是应该在for循环里吗?就每次走完while循环以及if之后重新回到for循环的时候cnt都应该重新变成0才对吧??
没有题解,我这辈子听不懂
这里给个样例分析,针对怎么拆分
原来是对每组数的s进行拆分啊,针对样例
“1 2 3
2 4 1
3 4 3
4 5 2”来说,第一组拆成2个(3 = 1 + 2),第二组拆成1个(1 = 1),第三组拆成2个(3 = 1 + 2),第三组拆成2个(3 = 1 + 2)
多重背包二进制优化和快速冥基本是一个思想吧,和TCP拥塞控制窗口BIC算法基本也是一个思想吧
多重背包二进制优化就是把每一堆总的体积和价值看成一个新合成物体的体积和价值,然后就有点类似0-1背包了
二进制优化一个坑点在于:新物体数目会多于2000,否则就segment fault,我直接踩坑
对的,需要将每一个s都转化为多堆,根据二进制来分堆
快速”冥”没绷住