乘法原理
乘法原理是说把多个步骤的所有方法相乘,表示整个事件所有可能的解决方法
原题
有 N� 种物品和一个容量是 V� 的背包。
第 i� 种物品最多有 si�� 件,每件体积是 vi��,价值是 wi��。
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。
输入格式
第一行两个整数,N,V�,�,用空格隔开,分别表示物品种数和背包容积。
接下来有 N� 行,每行三个整数 vi,wi,si��,��,��,用空格隔开,分别表示第 i� 种物品的体积、价值和数量。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N≤10000<�≤1000
0<V≤20000<�≤2000
0<vi,wi,si≤20000<��,��,��≤2000
提示:
本题考查多重背包的二进制优化方法。
输入样例
4 5
1 2 3
2 4 1
3 4 3
4 5 2
输出样例:
10
|-----------------|
| 难度:中等 |
| 时/空限制:1s / 64MB |
| 总通过数:75903 |
| 总尝试数:164479 |
| 来源:背包九讲 , 模板题 |
| 算法标签 |
挑战模式
原题链接
传送门
代码
cpp
#include<bits/stdc++.h>
using namespace std;
const int N=11000+10,M=2000+10;
int v[N],w[N];
int f[M];
int main()
{
int n,m;
scanf("%d%d",&n,&m);
int cnt=0;
for(int i=0;i<n;i++)
{
int a,b,s;
scanf("%d%d%d",&a,&b,&s);
int k=1;
while(k<=s)
{
cnt++;
v[cnt]=k*a;
w[cnt]=k*b;
s-=k,k*=2;
}
if(s>0)
{
cnt++;
v[cnt]=s*a;
w[cnt]=s*b;
}
}
n=cnt;
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]);
}
}
printf("%d\n",f[m]);
return 0;
}
总结
一、二进制优化
1.不优化的情况的时间复杂度是N^3,在数据范围比较大的时候会超时,所以我们选择二进制优化,把一维的N优化为logN,时间复杂度变为N^2*logN,可以通过这道题
2.思路是,给定了物品数目s,可以选择0件,1件,2件,......,s件物品,一个一个循环的话需要循环s+1次才能表示所有的情况,如果我们使用二进制的话,int范围内都只需要32位数字,就可以表示所有数字,数据范围是2000,只需要最多11个数字,2^11=2048,2^0,2^1,2^2,...2^11,二进制表示本质和01背包就很相似,每一个数位选择0或者是1,从而可以表示所有数字
3.注意一个一般化的情况,物品的件数s不是2的整数次幂,比如数字10,2^0=1,2^1=2,2^2=4,2^3=8,0+1+2+4+8>10,如果选择到数字2^3=8,超过了我们最多可以选择的件数10,这会造成理论上可以选择多件物品,但是实际上只有10件物品可以选择,所以我们只能选择到2^2=4,0+1+2+4=7,再补充一个数字10-7=3,原来的0,1,2,4可以表示0~7范围的所有数字,加上数字3之后,可以表示0~10范围的所有数字,0,1,2,4表示0~7范围内所有数字的方法就是二进制计数,每一个数位是否选择该数字
-
-
- -:000 0 0
-
-
-
-
- :001 1 1
-
-
-
-
- -:010 2 2
-
-
-
- -:011 3 1+2
-
-
-
- -:100 4 4
-
-
-
- -:101 5 4+1
-
-
-
-
- :110 6 4+2
-
-
-
-
-
- :111 7 4+2+1
-
-
二、代码细节
1.表示物品的数组的容量是 1000*11(二进制优化),表示答案的数组的容量是2000,都增加10防止数组越界
2.本质是把一件物品拆成多组物品,是否选择该组物品,从而把多重背包转换成01背包来进行求解
3.用一个计数器来存数组下标,和单链表的dix类似,while循环之后加一个if特判,表示最后一种情况,和质因数分解类似,也是判断最后处理的结果
4.原来的n件物品变成了现在的cnt件物品,因为计数器从1开始作为数下标存储物品的体积和价值,所以套用01背包模板的时候要从1开始遍历,01背包模板的第二层循环体积从大到小遍历