题目描述:多重背包问题II
有 N种物品和一个容量是 V 的背用。
i 种物品最多有 si 件,每件体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。
接下来有 N 行,每行两个整数 vi, wi,si 用空格隔开,分别表示第 i 件物品的体积和价值和数量。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N≤1000
0<V≤2000
0<vi,wi,si≤2000
输入样例
4 5
1 2 3
2 4 1
3 4 3
4 5 2
输出样例
10
基本思考框架
思路和多重背包问题I一样,但这题的数据范围变成1000了,非优化写法时间复杂度O(n^3) 接近 1e9
必超时。
优化多重背包的优化
首先,我们不能用完全背包的优化思路来优化这个问题,因为每组的物品的个数都不一样,是不能像之前一样推导不优化递推关系的。(详情看下面引用的博文)
引用我之前写的博客:动态规划-完全背包问题
我们列举一下更新次序的内部关系:
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]+2*w , .....)
由上两式,可得出如下递推关系:
f[i][j]=max(f[i,j-v]+w , f[i-1][j])
接下来,我介绍一个二进制优化的方法,假设有一组商品,一共有11个。我们知道,十进制数字 11 可以这样表示
$$
11 = 1011(B) = 0111(B) + (11-0111(B)) = 0111(B) + 0100(B)
$$
正常背包的思路下,我们要求出含这组商品的最优解,我们要枚举12次(枚举装0,1,2....12个)。
现在,如果我们把这11个商品分别打包成含商品个数为1个,2个,4个,4个(分别对应0001,0010,0100,0100)的四个”新的商品 “, 将问题转化为01背包问题,对于每个商品,我们都只枚举一次,那么我们只需要枚举四次 ,就可以找出这含组商品的最优解。 这样就大大减少了枚举次数。
这种优化对于大数尤其明显,例如有1024个商品,在正常情况下要枚举1025次 , 二进制思想下转化成01背包只需要枚举10次。
优化的合理性的证明
先讲结论:上面的1,2,4,4是可以通过组合来表示出0~11中任何一个数的,还是拿11证明一下(举例一下):
首先,11可以这样分成两个二进制数的组合:
$$
11 = 0111(B) + (11 - 0111(B)) =0111(B) + 0100(B)
$$
其中0111通过枚举这三个1的取或不取(也就是对0001(B),0010(B),0100(B)的组合),可以表示十进制数0~7( 刚好对应了 1,2,4 可以组合出 0~7 ) , 0~7的枚举再组合上0100(B)( 即 十进制的 4 ) ,可以表示十进制数 0~11。其它情况也可以这样证明。这也是为什么,这个完全背包问题可以等效转化为01背包问题,有木有觉得很奇妙
怎么合理划分一个十进制数?
上面我把11划分为
$$ 11 = 0111(B) + (11 - 0111(B)) =0111(B) + 0100(B) $$
是因为 0111(B)刚好是小于11的最大的尾部全为1的二进制 ( 按照上面的证明,这样的划分没毛病 ) , 然后那个尾部全为1的数又可以 分解为 0000....1 , 0000....10 , 0000....100 等等。
对应c++代码:
//设有s个商品,也就是将s划分
for(int k = 1 ; k <= s ;k*=2)
{
s-=k;
goods.push_back({v*k,w*k});
}
if(s>0)
goods.push_back({v*s,w*s});
终究AC代码:01优化+二进制优化
ac代码
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
const int N = 2010;
int f[N],n,m;
struct good
{
int w,v;
};
int main()
{
cin>>n>>m;
vector<good> Good;
good tmp;
//二进制处理
for(int i = 1 ; i <= n ; i++ )
{
int v,w,s;
cin>>v>>w>>s;
//坑,k <= s
for(int k = 1 ; k <= s ; k*=2 )
{
s-=k;
Good.push_back({k*w,k*v});
}
if(s>0) Good.push_back({s*w,s*v});
}
//01背包优化+二进制
for(auto t : Good)
for(int j = m ; j >= t.v ; j--)
f[j] = max(f[j] , f[j-t.v]+t.w ); //这里就是f[j]
cout<<f[m]<<endl;
return 0;
}
如果还不明白的童鞋可以看看这篇click here,感觉讲的很详细
还是这个更清晰
good tmp; 是多余的
请问为什么要用for(auto t:Good)呢?
还有Good.v为啥不行啊
需要一个迭代器(类似指针)来遍历容器
Good是一个vector,good才可以good.v
zro
orz
为什么用二维的01背包会超时
这个题的数据范围比01背包的大
Good.push_back({kw,kv});
这样会比下面这个快吗
v[cnt] = vv * k;
w[cnt] = ww * k;
01优化是什么意思?转化成01背包的优化吗?
yes,思路就是将多个一样的商品转化成数量更少可能不一样只可用一次的背包即01背包,这些数量更少的背包也能组合出0到原来数量的所有背包
学到了,大佬orz
%%%
请问 数据范围怎么开的
%%%%%%%%%%%%%%%%%%%%%%%%%%%%
tql
也难怪我总超时
tql
tql
帅!
时间复杂度是多少
为啥会存在s拆不干净的情况呢
比如你s是9,k从1开始拆, 1 + 2 + 4 == 7, s不就差个2没拆完
大佬弱弱的问下,二进制优化版本时间复杂度多少哇?
$NVlogS$
感谢,当时傻住了。。。