1. 程式人生 > 其它 >AcWing 171 送禮物

AcWing 171 送禮物

雙向DFS搜尋、剪枝

AcWing 171 送禮物

題目描述

達達幫翰翰給女生送禮物,翰翰一共準備了 N 個禮物,其中第 i 個禮物的重量是 G[i]。
達達的力氣很大,他一次可以搬動重量之和不超過 W的任意多個物品。
達達希望一次搬掉儘量重的一些物品,請你告訴達達在他的力氣範圍內一次效能搬動的最大重量是多少。
1 <= N <= 46 , 1 <= W, G[i] <= 231 - 1

輸入格式

20 5
7
5
4
18
1

輸出格式

19

題目分析:

看到這題一開始容易想到揹包問題,“力氣範圍內一次效能搬動的最大重量”,明顯是一個01揹包問題,然而一看資料,物品個數很少,而揹包容量很大,首先開不了揹包陣列,其次01揹包複雜度為O(NV),即46 * 231

,絕對會被T掉。

由於物品個數很少,我們考慮用搜索解決,每個物品有兩個狀態,一共有246種狀態計算,也會TLE。

題目告訴我們最後選擇的物品重量不超過W,這是問題的”終態“,在已知初態和終態的情況下,我們可以分別從兩邊開始搜尋(雙向搜尋),產生兩顆深度減半的搜尋樹,在中間交會、組合成最終的答案。

雙向搜尋和迭代加深一樣,可以避免DFS在深層子樹上浪費時間。

先對前半部分搜尋,用陣列儲存所有選擇所產生的重量,再對後半部分搜尋,每次選擇求得一個後半部分產生的總重量ans,我們只需要在前面找出最大但不大於W - ans的重量,在ans確定的情況下,只有這兩個相加才可能產生最優解,更新即可。

對儲存前半部分重量的陣列排序、去重(降低二分複雜度),即可採用二分法求得ans對應的前半部分的重量。

複雜度分析:

前半部分,只有2N/2種狀態,複雜度為O(2N/2),後半部分在前半部分的基礎上,對每次的結果還要進行一次二分,因此第二部分為O(2N/2 * log N/2),總時間複雜度為O(2N/2 * log N/2)。

對常數的優化

  1. 優化搜尋順序

    對於每個物品,在選和不選都有兩種狀態,但如果選了之後總重量超過了W,那一定不可能成為解,直接剪枝即可。這裡如果當前重量越大,越可能發生剪枝從而減少分支結點數量,我們可以對物品按重量從大到小排序。

  2. 選取適當的“折半劃分點”

    我們沒有必要一定按對稱的數量劃分前後部分,事實上,前半部分複雜度比後半部分少了一個log n,我們可以讓前半部分多搜尋幾個物品,以達到在常數上均衡複雜度的效果。


程式碼如下

#include <iostream>
#include <algorithm>
using namespace std;

const int N = 46;

typedef long long LL;

int w, n, cnt;
int v[N];
LL w1[(1 << 24) + 1], res;

void dfs1 (int cur, LL weight) { // 前1~(n/2)個物品能夠湊出來的體積 /  當前的重量
    if (cur == n / 2 + 1) {
        w1[++cnt] = weight;
        return;
    }
    dfs1(cur + 1, weight);
    if (weight + v[cur] <= w) // 剪枝
        dfs1(cur + 1, weight + v[cur]);
}

void dfs2 (int cur, LL weight) {
    if (cur == n + 1) {
        int ans = *lower_bound(w1+1, w1+cnt+1, w - weight, greater<int>());
        // int ans = 0;
        res = max(res, ans + weight);
        return;
    }
    dfs2(cur + 1, weight);
    if (weight + v[cur] <= w) // 剪枝
        dfs2(cur + 1, weight + v[cur]);
}

int main () {
    cin >> w >> n;
    for (int i = 1; i <= n; i++) cin >> v[i];
    sort(v+1, v+n+1, greater<int>()); // 降序
    dfs1(1, 0);
    sort(w1+1, w1+cnt+1, greater<int>()); // 去重
    cnt = unique(w1+1, w1+cnt+1) - (w1+1); // 返回去重後的陣列個數
    dfs2(n / 2 + 1, 0);
    cout << res << endl;
    return 0;
}