AcWing 171. 送禮物(dfs+二分)

題目傳送門: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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章