1. 程式人生 > 其它 >超大揹包問題 [cdq分治]

超大揹包問題 [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;
}