Description
有 NNN 个重量和价值分别为 wiw_iwi 和 viv_ivi 的物品。从这些物品中挑选出总重量不超过 WWW 的物品放入背包中,求背包里物品价值总和的最大值。
Input
N Wv1 w1v2 w2......vN wNN\space W \\ v_1\space w_1\\ v_2\space w_2 \\ ......\\ v_N\space w_NN Wv1 w1v2 w2......vN wN
Output
输出背包里物品价值总和的最大值
Sample Input
4 5
4 2
5 2
2 1
8 3
Sample Output
13
Hint
1≤N≤401 ≤ N ≤ 401≤N≤40
1≤vi≤10151 ≤ v_i ≤ 10^{15}1≤vi≤1015
1≤wi≤10151 ≤ w_i ≤ 10^{15}1≤wi≤1015
1≤W≤10151 ≤ W ≤ 10^{15}1≤W≤1015
分析
还以为又能水一道背包模板,没想到啊
其实这道题的突破点很明显,我们可以看到N≤40N \leq 40N≤40,不难想到用暴力大法
但如果直接暴力,复杂度为O(2n)O(2^n)O(2n),就算加上剪枝还是只有70分
那不就过不了了吗
所以,为了优化这个复杂度,我们想到分治
我们可以将nnn拆成两半后再枚举,这样复杂度就变成了两个2n/22^{n/2}2n/2,具有可行性,这样就可以将问题转化为在前半找到质量和价值为w1,v1w_1,v_1w1,v1后,在后半部分找w2w_2w2使在w1+w2≤Ww_1+w_2 \leq Ww1+w2≤W时v2v_2v2的最大值
现在要考虑的问题就成了如何在所有(w2,v2)(w_2,v_2)(w2,v2)高效的找到这个满足条件的最大的v2v_2v2
这有些像我们当时学二分答案的样子了,所以我们想到将这个集合排序并去除明显的非最优解
明显的非最优解值(wi,vi)(w_i,v_i)(wi,vi)是指存在一个(wj,vj)(w_j,v_j)(wj,vj)使wj≤wi && vj≥viw_j \leq w_i \space \space \&\& \space \space v_j \geq v_iwj≤wi && vj≥vi
这样去掉后就是一个简单的二分搜索了
标程
#include <cstdio>
#include <iostream>
#include <algorithm>
#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;
}