NYOJ 还是01背包(枚举+二分)

还是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;
}

 

 

 

 

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值