超大背包问题
使用dp求解复杂度过高,利用n比较小的特点。
折半枚举,把前半部分中的选取方法对应的重量和价值总和记为w1,v1,这样在后半部分寻找总重w2<=W-w1时使v2最大的选取方法就好。
首先排除所有w2[i]<=w2[j]&&v2[i]>=v1[j]的j。这一点可以按照w2,v2的字典序排序后做到。
此后剩余的元素都满足w2[i]<w2[j]&&v2[i]<v2[j],要计算max{v2|w2<=W'},只要寻找满足w2[i]<=W'的最大i即可,用二分搜索完成。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<iostream> #include<math.h> #include<algorithm> #include<stdio.h> #define INF 10000000 typedef long long ll; using namespace std; int n; ll w[41],v[41],W; pair<ll,ll> ps[1<<21]; int main() { cin>>n; for(int i=0;i<n;i++) { cin>>w[i]>>v[i]; } cin>>W; //枚举前半部分 二进制枚举 int n2=n/2; for(int i=0;i<1<<n2;i++) { ll sw=0,sv=0; for(int j=0;j<n2;j++) { if(i>>j&1) { sw+=w[j]; sv+=v[j]; } } ps[i]=make_pair(sw,sv); } //去掉多余元素(价值小重量大) sort(ps,ps+(1<<n2)); int m=1; for(int i=1;i<1<<n2;i++) { if(ps[m-1].second<ps[i].second) { ps[m++]=ps[i]; } } //枚举后半部分并求解 ll res=0; for(int i=0;i<1<<(n-n2);i++) { ll sw=0,sv=0; for(int j=0;j<n-n2;j++) { if(j>>i&1) { sw+=w[n2+j]; sv+=v[n2+j]; } } if(sw<=W) { //找到前半部分中加起来不超重的价值最大者 ll tv=(lower_bound(ps,ps+m,make_pair(W-sw,(ll)INF))-1)->second; res=max(res,sv+tv); } } cout<<res<<endl; return 0; }