超大背包问题(折半枚举)

超大背包问题

 

有重量和价值分别为w(i),v(i)的n个物品,从这些物品中选出总重不超过W的物品,求所有挑选方案中价值总和的最大值

1<=n<=40

1<=w(i),v(i)<=1e15

1<=W<=1e15

 

输入

第一行为n,接着输入一行w(i)和一行v(i),最后输入W占单独一行

 

输出

单独一行即所有挑选方案中价值总和的最大值

 

样例

Input

4

2 1 3 2

3 2 4 2

5

 

Output

7

 

思路

这个问题和普通的01背包一样,只是w,v的数据范围太大了,没法用常规的dp解决。但我们注意到了n很小,而每种物品只有选择和不选两种状态,所以可以尝试枚举所有物品的选择情况,但是复杂度已经达到O(2^40),是行不通的。所以这里我们考虑用折半枚举的思想,先把前面一半的物品的选择情况全部枚举出来,计入ps数组中(first是重量,second是价值),并按重量升序排序以便接下来的二分搜索。这时我们可以去除一些不必要的选择情况,当ps[i].first<ps[j].first&& ps[i].second>ps[j].second时我们一定不会选择第j种方案了,所以可以去掉。之后我们就继续把剩余物品的选择情况枚举出来,每枚举出来一种方案,让该方案和ps中重量first不超过W-sw的方案结合便得到一组解,记录最优解即可。


代码

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;

const int maxn = 50;
const ll INF = 1e18;

int n;
ll w[maxn], v[maxn], W;

pair<ll, ll> ps[1 << (maxn / 2)];//重量,价值

void solve() {
	int n2 = n / 2;
	//枚举0到n2-1的物品选择情况
	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);
	}

	//去除肯定不选择的情况(这部分代码要多看几遍)
	int cnt = 1;
	sort(ps, ps + (1 << n2));//按重量排序
	for (int i = 1; i < 1 << n2; i++) {
		if (ps[cnt - 1].second < ps[i].second) {
			ps[cnt] = ps[i];
			cnt++;
		}
	}

	//枚举剩下物品的选择情况并更新结果
	ll ans = 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[j + n2];
				sv += w[j + n2];
			}
		}

		if (sw < W) {
			int pos = lower_bound(ps, ps + cnt, make_pair(W - sw, INF)) - ps;
			/*这里pos是不会等于0的,因为在枚举前n2个物品的时候,
			肯定有一种选择方案是所有物品都不选而这个方案排序之后就是ps[0],所以
			一定有ps[0].first == 0 < W - sw,
			所以lower_bound(ps, ps + cnt, make_pair(W - sw, INF)) - ps >= 1
			*/
			if (ps[pos].first > W - sw || cnt == pos) pos--;//注意细节
			ans = max(ans, sv + ps[pos].second);
		}
		/*
		lower_bound(ps, ps + cnt, make_pair(W - sw, INF)会寻找数组ps中first值
		第一个大于等于W - sw的元素,自己测试了下这里的second对搜索结果没有影响。
		*/
	}
	printf("%lld\n", ans);
}

int main() {
	while (scanf("%d", &n) == 1) {
		for (int i = 0; i < n; i++) scanf("%lld", &w[i]);
		for (int i = 0; i < n; i++) scanf("%lld", &v[i]);
		scanf("%lld", &W);
		solve();
	}
	return 0;
}


posted @ 2017-10-29 23:37  不想吃WA的咸鱼  阅读(346)  评论(2编辑  收藏  举报