有 N N N 个重量和价值分别为 w i w_i wi 和 v i v_i vi 的物品。从这些物品中挑选出总重量不超过 W W W 的物品放入背包中,求背包里物品价值总和的最大值。
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
输出背包里物品价值总和的最大值
4 5
4 2
5 2
2 1
8 3
13
1 ≤ N ≤ 40 1 ≤ N ≤ 40 1≤N≤40
1 ≤ v i ≤ 1 0 15 1 ≤ v_i ≤ 10^{15} 1≤vi≤1015
1 ≤ w i ≤ 1 0 15 1 ≤ w_i ≤ 10^{15} 1≤wi≤1015
1 ≤ W ≤ 1 0 15 1 ≤ W ≤ 10^{15} 1≤W≤1015
还以为又能水一道背包模板,没想到啊
其实这道题的突破点很明显,我们可以看到 N ≤ 40 N \leq 40 N≤40,不难想到用暴力大法
但如果直接暴力,复杂度为 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+w2≤W时 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 wj≤wi && vj≥vi
这样去掉后就是一个简单的二分搜索了
#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;
}