超大背包问题(折半枚举, 双向搜索)
有重量和价值分别为wi, vi (1 <= wi, vi <= 1e15)的n (1 <= n <= 40)个物品,从中挑选总重不超过W(1 <= W <= 1e15)的物品,求价值总和最大值。
这是典型的01背包问题,不过dp求解复杂度为O(nW),这里W太大了,因此无法求解。挑选物品方法共有2^n种,也无法直接枚举。但是拆成两半再枚举的话还是可行的,每部分最多只有20个。假设第一部分某个选取方法对应的重量和价值为w1, v1,那么只要在第二部分中寻找w2+w1<=W且v2最大的方法就行了。寻找时可以用二分查找,总时间复杂度为O(2^(n/2)n),可以接受。
#include <iostream>
#include <sstream>
#include <fstream>
#include <string>
#include <map>
#include <vector>
#include <list>
#include <set>
#include <stack>
#include <queue>
#include <deque>
#include <algorithm>
#include <functional>
#include <iomanip>
#include <limits>
#include <new>
#include <utility>
#include <iterator>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <cmath>
#include <ctime>
using namespace std;
typedef long long LL;
const int maxn = 50;
const LL INF = 0x3f3f3f3f;
int n;
LL w[maxn], v[maxn];
LL W;
pair<LL, LL> ps[1<<(maxn/2)]; // (重量, 价值)
void solve()
{
//枚举前半部分
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 ((i >> j) & 1)
{
sw += w[n2 + j];
sv += v[n2 + j];
}
if (sw <= W)
{
LL tv = (lower_bound(ps, ps + m, make_pair(W - sw, INF)) - 1)->second;
res = max(res, sv+tv);
}
}
printf("%lld
", res);
}
int main()
{
while (~scanf("%d%lld", &n, &W))
{
for (int i = 0; i < n; ++i)
scanf("%lld%lld", &w[i], &v[i]);
solve();
}
return 0;
}
/*
Sample Input
4 5
2 3
1 2
3 4
2 2
Sample Output
7
*/