超大背包问题题解

Description

N N N 个重量和价值分别为 w i w_i wi v i v_i vi 的物品。从这些物品中挑选出总重量不超过 W W W 的物品放入背包中,求背包里物品价值总和的最大值。

Input

N   W v 1   w 1 v 2   w 2 . . . . . . v N   w N N\space W \\ v_1\space w_1\\ v_2\space w_2 \\ ......\\ v_N\space w_N N Wv1 w1v2 w2......vN wN

Output

输出背包里物品价值总和的最大值

Sample Input

4 5
4 2
5 2
2 1
8 3

Sample Output

13

Hint

1 ≤ N ≤ 40 1 ≤ N ≤ 40 1N40
1 ≤ v i ≤ 1 0 15 1 ≤ v_i ≤ 10^{15} 1vi1015
1 ≤ w i ≤ 1 0 15 1 ≤ w_i ≤ 10^{15} 1wi1015
1 ≤ W ≤ 1 0 15 1 ≤ W ≤ 10^{15} 1W1015

分析

还以为又能水一道背包模板,没想到啊
其实这道题的突破点很明显,我们可以看到 N ≤ 40 N \leq 40 N40,不难想到用暴力大法
但如果直接暴力,复杂度为 O ( 2 n ) O(2^n) O(2n),就算加上剪枝还是只有70分
那不就过不了了吗
所以,为了优化这个复杂度,我们想到分治
我们可以将 n n n拆成两半后再枚举,这样复杂度就变成了两个 2 n / 2 2^{n/2} 2n/2,具有可行性,这样就可以将问题转化为在前半找到质量和价值为 w 1 , v 1 w_1,v_1 w1,v1后,在后半部分找 w 2 w_2 w2使在 w 1 + w 2 ≤ W w_1+w_2 \leq W w1+w2W v 2 v_2 v2的最大值
现在要考虑的问题就成了如何在所有 ( w 2 , v 2 ) (w_2,v_2) (w2,v2)高效的找到这个满足条件的最大的 v 2 v_2 v2
这有些像我们当时学二分答案的样子了,所以我们想到将这个集合排序并去除明显的非最优解
明显的非最优解值 ( w i , v i ) (w_i,v_i) (wi,vi)是指存在一个 ( w j , v j ) (w_j,v_j) (wj,vj)使 w j ≤ w i    & &    v j ≥ v i w_j \leq w_i \space \space \&\& \space \space v_j \geq v_i wjwi  &&  vjvi
这样去掉后就是一个简单的二分搜索了

标程

#include 
#include 
#include 
#define MAX_N 40
using namespace std;
long long INF = 0x3F3F3F3F3F3F3F3FLL;   //设为 (1<<20) 也可以
pair< long long, long long > ps[1 << (MAX_N / 2)];   //(重量,价值)

int main()
{
	long long w[MAX_N], v[MAX_N];
	long long N, W;
	
	
	freopen("knapsack.in","r",stdin);
	freopen("knapsack.out","w",stdout);
	scanf("%lld %lld", &N, &W);
	for (int i = 0; i < N; i++)
		scanf("%lld %lld", &v[i], &w[i]);
		 
	//枚举前半部分 O(2^N/2)
	int N2 = N / 2;
	for (int i = 0; i < 1 << N2; i++)
	{
		long long sw = 0LL, sv = 0LL;
		for (int j = 0; j < N2; j++)
		{
			if (i >> j & 1)
			{
				sw += w[j];
				sv += v[j];
			}
		}
		ps[i] = make_pair(sw, sv);
	}
	
	//去除多余的元素: sw[i] <= sw[j] 并且 sv[i] >= sv[j] 
	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];
	
	//枚举后半部分 O(2^N/2)并求解
	long long res = 0LL;
	for (int i = 0; i < 1 << (N - N2); i++)
	{
		long long 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, INF)) - 1) -> second;
			res = max(res, sv + tv);
		}
	}
	printf("%lld\n", res);
	fclose(stdin);
	fclose(stdout); 
	 
	return 0; 
}

你可能感兴趣的:(题解)