題目傳送門:https://www.acwing.com/problem/content/173/
【題目描述】
達達幫翰翰給女生送禮物,翰翰一共準備了N個禮物,其中第i個禮物的重量是G[i]。達達的力氣很大,他一次可以搬動重量之和不超過W的任意多個物品。達達希望一次搬掉儘量重的一些物品,請你告訴達達在他的力氣範圍內一次性能搬動的最大重量是多少。
【輸入格式】
第一行兩個整數,分別代表W和N。
以後N行,每行一個正整數表示G[i]。
【輸出格式】
僅一個整數,表示達達在他的力氣範圍內一次性能搬動的最大重量。
【數據範圍】
1≤N≤45,
1≤W,G[i]≤231−1
【輸入樣例】:
20 5
7
5
4
18
1
【輸出樣例】:
19
分析:
該題是一個揹包問題,每個物品選還是不選,可以暴力枚舉子集,使得其和值最接近W.但是N<=45,時間上承受不了;那麼用動歸,可是它是一個大體積的問題,從空間上處理不了。
李煜東大神的《算法進階指南》上給出了一個有意思的解法:
1.將物品按重量降序排序,然後把物品分成前後兩半部分
2.對第一部分進行暴力深搜,記錄下所有的 可能產生的重量和,並升序排序後去重。
3.對第二部分進行暴力搜索,當搜到一種可能產生的重量和值時,去第一部分搜索產生的重量和值中進二分查找可以湊上的最大的數,並保證和值在w之內,維護一個最優值。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
int n,cnt;
LL w,g[50],ans,s[(1<<24)+6];
bool cmp(LL a,LL b){
return a > b;
}
void find(LL val){ //二分查找滿足題意的最大值
LL left = 1,right = cnt+1, goal = w - val;
while(left+1 < right){
LL mid = (left + right)/2;
if(s[mid] <= goal) left = mid;
else right = mid;
}
ans = max(ans , s[left]+val);
}
void dfs(int k,LL sum){//第一遍搜索,對前半部分的物品進行爆搜
//cout << k << endl;
if(k == n/2+1){
s[++cnt] = sum;
return;
}
dfs(k+1,sum);//不裝第k個數.
if(sum + g[k] <= w) //裝第k個數。
dfs(k+1,sum + g[k]);
}
void dfs2(int k,LL sum ){//第二遍搜索,對後半部分的物品進行爆搜
if(k == n+1){
find(sum); //在前半部分產生的和值中去尋找剩下最大值。
return;
}
dfs2(k+1,sum);
if(sum + g[k] <= w){
dfs2(k+1,sum + g[k]);
}
}
int main(){
cin >> w >> n;
for(int i = 1;i<= n; i++){
cin >> g[i];
}
sort(g+1,g+1+n,cmp);
dfs(1,0);
sort(s+1,s+cnt+1);
//unique去重.
cnt = unique(s+1,s+1+cnt) - (s+1);
dfs2(n/2+1,0);
cout << ans << endl;
return 0;
}