AcWing 171 送禮物
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
由於物品個數很少,我們考慮用搜索解決,每個物品有兩個狀態,一共有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)。
對常數的優化
-
優化搜尋順序
對於每個物品,在選和不選都有兩種狀態,但如果選了之後總重量超過了W,那一定不可能成為解,直接剪枝即可。這裡如果當前重量越大,越可能發生剪枝從而減少分支結點數量,我們可以對物品按重量從大到小排序。
-
選取適當的“折半劃分點”
我們沒有必要一定按對稱的數量劃分前後部分,事實上,前半部分複雜度比後半部分少了一個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;
}