还是01背包
时间限制:10000 ms | 内存限制:228000 KB
难度:5
描述
有n个重量和价值分别为 wi 和 vi 的物品,从这些物品中挑选总重量不超过W的物品,求所有挑选方案中价值总和的最大值。
输入
多组测试数据。
每组测试数据第一行输入n 和 W ,接下来有n行,每行输入两个数,代表第i个物品的wi 和 vi。
1 <= n <=40
1 <= wi <= 10^15
1 <= vi <= 10^15
1 <= W <= 10^15
输出
每组数据输出一行,代表挑选方案中价值总和的最大值。
样例输入
4 5
2 3
1 2
3 4
2 2
样例输出
7
这题写了好久,调了好久,心塞。
这题解题思路是这样的,首先看一下vi, wi, W便知太大了,所有的背包动态规划方程都显得无能为力了。看一下n<=40, 没办法了只能从这边入手了。怎么做呢?没想到其他的,直接枚举就是了。但是枚举也是需要技巧的,40个得有2^40的量,会崩溃的额,也就是说时间复杂度为O(2^n);接下来就是要做优化工作了。其实以前做题的时候也做过类似思想的题,就是把n个物品分成两半,每一半就剩下n/2个,这样复杂度降的很多。用一个整数来表示方案,用到位运算来表示,例如2的二进制位11,这表示第一个和第二个都拿,又如1的二进制数为01, 这表示第一个拿,第二个物品不拿,这样表示简洁简单且速度快。设前半部分某种方案是得到的重量为w1, 价值为v1,这在第二部分中w2,v2必定有w2+w1<=W; 所以就枚举第一部分,再去第二部分中找w2<=W-w1时价值的最大值。然后第二部分开始时就应该按wi,,vi的字典序排序,因为如果有wi<wj,vi>vj那么i方案一定优于j方案。所以在第二部分中排完序之后会剔除掉某些方案。然后既然排好序就得按二分思想来查找。
AC代码:
# include <cstdio>
# include <algorithm>
using namespace std;
typedef long long int ll;
struct goods{
ll w;
ll v;
};
goods s1[30], s2[30];//输入数据
goods change_s2[(1<<20)+10];//转化为w与v的和
int compare(goods a, goods b){//sort中的比较函数
if(a.w!=b.w){
return a.w<b.w;
}
return a.v<b.v;
}
int main(){
int n, i, v1, v2, cur1, cur2, part1, part2, _cur1, _cur2, left, right, mid;
ll sum_w, sum_v, W, num_set, temp, now_w, now_v, w2, ans;
while(scanf("%d%lld", &n, &W)!=EOF){
part1=n/2;
part2=n-part1;
for(i=1; i<=part1; i++){
scanf("%lld%lld", &s1[i].w, &s1[i].v);
}
for(i=1; i<=part2; i++){
scanf("%lld%lld", &s2[i].w, &s2[i].v);
}
cur2=1;
//枚举第二部分中的所有结果,并存储起来 ,存储的
//是每种方案中w和v的和
for(num_set=0; num_set<=(1<<part2)-1; num_set++){
temp=num_set;
sum_w=0;sum_v=0;
for(i=1; i<=part2; i++){
if(temp&1){
sum_w=sum_w+s2[i].w;
sum_v=sum_v+s2[i].v;
}
temp=temp>>1;
}
change_s2[cur2].w=sum_w;
change_s2[cur2].v=sum_v;
cur2++;
}
//排序
sort(change_s2+1, change_s2+cur2, compare);
now_v=-1;_cur2=1;
//剔除掉一些不符合的方案
for(i=1; i<=cur2-1; i++){
if(change_s2[i].v>now_v){
change_s2[_cur2].v=change_s2[i].v;
change_s2[_cur2].w=change_s2[i].w;
_cur2++;
now_v=change_s2[i].v;
}
}
ans=0;
//枚举第一部分的所有情况并到第二部分二分搜索
for(num_set=0; num_set<=(1<<part1)-1; num_set++){
sum_w=0;sum_v=0;
temp=num_set;
for(i=1; i<=part1; i++){
if(temp&1){
sum_w=sum_w+s1[i].w;
sum_v=sum_v+s1[i].v;
}
temp=temp>>1;
}
if(sum_w<=W){
w2=W-sum_w;
left=1;right=_cur2-1;
//二分搜索
while(left<=right){
mid=(left+right)/2;
if(change_s2[mid].w<=w2){
left=mid+1;
if(ans<sum_v+change_s2[mid].v){
ans=sum_v+change_s2[mid].v;
}
}
else{
right=mid-1;
}
}
}
}
printf("%lld\n", ans);
}
return 0;
}