超大背包问题另外解法 [cdq分治][乱搞]
题面:
有重量和价值分别为 wi ( 1 ≤ wi ≤ 1015 )、vi ( 1 ≤ vi ≤ 1015 ) 的 n (1 ≤ n ≤ 40 ) 个物品。
从这些物品中挑选总重量不超过 C ( 1 ≤ C ≤ 1015 ) 的物品,求所选挑选方案中价值总和的最大值。
思路:
重量可以很大,dp行不通了。物品数量很少,但也没少到可以直接枚举的程度。网上的做法都是折半之后枚举,但跑起来也不是特别快。抱着试一试的心态,用自己的想法做了,没想到速度快了很多。在自家 oj 上折半枚举的做法是一千多毫秒,这个方法只需要5~6毫秒。
基本思路就是分治,超过一个物品就先二分递归处理。
当只有一个物品而且背包容量够的话,就只有放和不放两种情况。
对于有多个物品的情况,我们可以从递归的下一层分别获得左、右两半边方案的集合。那么这一整块的一种方案就是左右两部分各自任取一种然后组合起来(并确保容量足够)。得到这一层的方案后,我们再筛去那些又重又不值钱的方案。
我们在每一层筛去非最优状态时,会对方案按照重量升序排序,可以发现我们这一层最后的方案集合仍然是保持这个性质的,那在递归的最顶层,即处理所有物品时,我们仍可以应用折半枚举时使用的二分查找,不再把结果存入数组,直接维护最大值即可。
为什么比折半枚举快这么多,还是枚举循环过程中遇到的无用状态(又重又不值钱)太多,常数也略大,而分治每一层都可以去掉非最优的状态。
目测学校的数据是随机数据,此方法在大部分情况下表现都很好,远超枚举,不过卡满数据时,每层都无法筛去任何一个方案的情况下,此时分治方案表现会比枚举的方法慢0.3倍左右,原因是筛方案时进行了无任何成果的内存操作。或许组合方案时要尝试使用单调队列等方式去优化而不是枚举组合后再筛?但这样子在能筛去方案的时候可能会产生更多的内存操作导致速度变慢吧(
实现:
我们需要保存每一个二分得到的区块的所有方案,需要三个维。但是我们是递归处理的,对于每一个起始位置,长的区间总是比短的区间后处理,所以去掉一个维,我们用 vector<way> mst[N] 储存所有方案,mst[i] 就表示起始位置为 i 的区间的所有可能方案。方案两两组合的时候就是 枚举 mst[左区间起始位置] 与 mst[右区间起始位置],用 一个临时数组 comb 记录组合后的方案。然后我们将 comb 按照重量升序排序,可以筛掉非最优的方案,我们清空 mst[l],然后把剩下的方案塞到里面去,这样做就能保证上一层读取 mst 对应位置时是正确的。函数的返回值作为答案,也就是除了递归最顶层,下层的返回值都是没用的。
PS: 这个递归应该是可以改成循环处理的,但我想不到换成循环以后能优化到什么地方,递归层数很少,差别不大,懒了,不改。
查看代码#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int MAX_N = 40;
using way = pair<ll, ll>;
way w[MAX_N];
vector<way> comb;
vector<way> mst[MAX_N];
ll cdq(int l, int r, int n, ll c)
{
if (l == r)
{
mst[l].clear();
mst[l].emplace_back(0, 0);
if (w[l].first <= c) mst[l].emplace_back(w[l]);
return 0;
}
int mid = (l + r) >> 1;
cdq(l, mid, n, c), cdq(mid + 1, r, n, c);
if (n == r - l + 1)
{
ll ans = 0;
auto &wrs = mst[mid + 1];
for (auto wl : mst[l])
{
int al = 0, ar = wrs.size() - 1;
while (al <= ar) {
auto am = (al + ar) >> 1;
if (wl.first + wrs[am].first <= c) al = am + 1;
else ar = am - 1;
}
ans = max(ans, wl.second + wrs[al - 1].second);
}
return ans;
}
for (auto wl : mst[l])
{
for (auto wr : mst[mid + 1])
{
if (wl.first + wr.first <= c)
{
comb.emplace_back(wl.first + wr.first, wl.second + wr.second);
}
else break;
}
}
sort(comb.begin(), comb.end());
mst[l].clear();
ll mi_v = -1;
for (auto &w : comb)
{
if (w.second > mi_v)
{
mi_v = w.second;
mst[l].emplace_back(w);
}
}
comb.clear();
return 0;
}
int main(void)
{
int n;
ll c;
while (~scanf("%d%lld", &n, &c))
{
for (int i = 0; i != n; ++i)
{
scanf("%lld%lld", &w[i].first, &w[i].second);
}
printf("%lld\n", cdq(0, n - 1, n, c));
}
return 0;
}

浙公网安备 33010602011771号