超大背包问题解题报告

超大背包问题:
有重量和价值分别为wi和vi的n个物品,从这些物品中挑选总重量不超过W的物品,
求所有挑选方案中价值总和最大值

限制条件:
1 <= n <= 40
1 <= wi, vi <= 10^15
1 <= W <= 10^15
分析:由于本题W巨大,因此是一道假动态规划题,如果用动态规划要么爆空间要么超时,于是我们可以抓住n极小的特点,如果将n二分为n/2那么将会产生复杂度为2^20一秒可过我们于是可以用折半二分。
补充一下本题需要用到的upper_bound和lower_bound。
lower_bound(begin,end,num)
二分查找数组中的begin位置到end-1位置二查找第一个大于或等于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。
upper_bound(begin,end,num)
二分查找数组中数组的begin位置到end-1位置第一个大于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。在从大到小的排序数组中,重载lower_bound()和upper_bound()
lower_bound(begin,end,greater())
二分查找数组中数组的begin位置到end-1位置第一个小于或等于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。
upper_bound(begin,end,greater())
二分查找数组中的begin位置到end-1位置第一个小于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。
附上代码

#include<bits/stdc++.h>
using namespace std;
const int maxn=40;
long long maxm=1<<20;
typedef long long ll;pair<ll,ll>ps[1<<(maxn)/2];
int main()
{
	ll w[maxn],v[maxn];
	ll n;
	ll W;
	cin>>n>>W;
		for(int i=0;i<n;i++)
		cin>>v[i]>>w[i];
	int n2=n/2;
	//对前半部分进行枚举 
	for(int i=0;i<1<<n2;i++)
	{
		ll sw = 0LL,sv=0LL;
		for(int j=0;j<n2;j++)
		{
			if(i>>j & 1)//二进制数i小于将1左移二十位,也就是二进制数i最多可有20个1 
			{			//每一位上如果是1代表取该位编号对应的物品。 
				sw+=w[j];
				sv+=v[j]; 
			}
		}
		ps[i]=make_pair(sw,sv);
	}
	int m=1;///去除多余的元素: sw[i] <= sw[j] 并且 sv[i] >= sv[j] 则删除j。 
	sort(ps,ps+(1<<n2));
	for(int i=1;i<1<<n2;i++)
		if(ps[m-1].second < ps[i].second )
			ps[m++]=ps[i];	
ll res=0LL;
	for(int i=0;i<1<<(n-n2);i++)
	{
		ll sw=0LL,sv=0LL;
		for(int j=0;j<n-n2;j++)
		{
			if(i>>j&1) 
			{
				sw += w[n2+j];
				sv += v[n2+j]; 
			}
			if(sw<=W)
			{
				long long tv = (lower_bound(ps, ps + m, make_pair(W - sw, maxm)) - 1) -> second;
				res = max(res, sv + tv);
			}
		} 
	}
	cout<<res;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值