超大背包问题:
有重量和价值分别为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;
}