前言
一开始看了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)我们知道任意一个实数可以由二进制数来表示,也就是20 2k其中一项或几项的和。
(3)这里多重背包问的就是每件物品取多少件可以获得最大价值。
分析:
-
如果直接遍历转化为01背包问题,是每次都拿一个来问,取了好还是不取好。那么根据数据范围,这样的时间复杂度是O(n3),也就是109,这样是毫无疑问是会TLE的。
-
假如10个取7个好,那么在实际的遍历过程中在第7个以后经过状态转移方程其实已经是选择“不取”好了。现在,用二进制思想将其分堆,分成k+1个分别有2k个的堆,然后拿这一堆一堆去问,我是取了好呢,还是不取好呢,经过dp选择之后,结果和拿一个一个来问的结果是完全一样的,因为dp选择的是最优结果,而根据第二点任意一个实数都可以用二进制来表示,如果最终选出来10个取7个是最优的在分堆的选择过程中分成了20=1,21=2,22=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∈[0,1023] (第11个箱子才能取到1024,评论区有讨论这个)都可以从这10个箱子里的苹果数量表示出来,但是这样选择的次数就是 ≤10次 。
比如:
- 如果要拿1001次苹果,传统就是要拿1001次;二进制的思维,就是拿7个箱子就行(分别是装有512、256、128、64、32、8、1个苹果的这7个箱子),这样一来,1001次操作就变成7次操作就行了。
这样利用二进制优化,时间复杂度就从 O(n3) 降到O(n2logS),从4∗109降到了2∗107。
代码
#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
大佬太经典了
大佬我大一,现在也走上算法之路了,向大佬看齐!!
我现在也大一,跟着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
二进制优化的核心不是划分成了二进制的方法,而是划分后可以通过01背包进行优化,用01背包优化多重背包,感觉有点套娃了
我直呼 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)