送礼物[CH2401]

题面描述
作为惩罚,GY被遣送去帮助某神牛给女生送礼物(GY:貌似是个好差事)但是在GY看到礼物之后,他就不这么认为了。某神牛有N个礼物,且异常沉重,但是GY的力气也异常的大(-_-b),他一次可以搬动重量和在 w ( w &lt; = 2 31 − 1 w(w&lt;=2^{31}-1 w(w<=2311)以下的任意多个物品。GY希望一次搬掉尽量重的一些物品,请你告诉他在他的力气范围内一次性能搬动的最大重量是多少。

输入格式
第一行两个整数,分别代表W和N。
以后N行,每行一个正整数表示 G [ i ] G[i] G[i], G [ i ] ≤ 2 31 − 1 G[i]≤ 2^{31}-1 G[i]2311

输出格式
仅一个整数,表示GY在他的力气范围内一次性能搬动的最大重量。
样例输入
20 5
7
5
4
18
1
样例输出
19
数据范围与约定
对于20%的数据 N &lt; = 26 N&lt;=26 N<=26
对于40%的数据 W &lt; = 2 26 W&lt;=2^{26} W<=226
对于100%的数据 N &lt; = 45     W ≤ 2 31 − 1 N&lt;=45~~~ W≤2^{31}-1 N<=45   W2311
思路
观察一下, n = 45 n=45 n=45时,若单纯的 2 n 2^{n} 2n爆搜的话,就 T L E TLE TLE
要不?优化搜索顺序?从大到小?
其实最重要的我们可以减小搜索状态,双向搜索,
即从初态(开始)和终态(结束)出发各搜索一半状态深度减半中间交会组合成最终的答案。
这可算是大剪枝啊。
其实这道题一眼01背包,奈何w太大
废话不说,代码上见。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cstdlib>
#include<cmath>
#define ui unsigned int 
using namespace std;
const int N=16677777;
int half,w;int a[N],s[N],m,n,arr[N];ui ans;
inline bool cmp(int a,int b){return a>b;}
void dfs1(int i,ui sum)//由于sum在加减时可能会溢出,所以要ui
{
	if(i==half){a[++m]=sum;return ;}
	dfs1(i+1,sum);
	if(sum+s[i]<=w)dfs1(i+1,sum+s[i]);
}
void calc(ui val)
{
	ui rest=w-val;int l=0,r=m;
	while(l<r){int mid=(l+r+1)>>1;if(a[mid]<=rest)l=mid;else r=mid-1;}
	ans=max(ans,val+a[l]);
}
void dec()
{
	int tot=0;sort(a+1,a+m+1);
	for(int i=1;i<=m;i++)
		if(i==1||a[i]!=a[i-1])
			arr[++tot]=a[i];
	m=tot;
	for(int i=1;i<=m;i++)a[i]=arr[i];
}
void dfs2(int i,ui sum)
{
	if(i==n+1){calc(sum);return ;}
	dfs2(i+1,sum);
	if(sum+s[i]<=w)dfs2(i+1,sum+s[i]);
}
int main()
{
	scanf("%d%d",&w,&n);m=0,ans=0;
	for(int i=1;i<=n;i++)scanf("%d",&s[i]);
	sort(s+1,s+n+1,cmp);
	half=(n>>1)+3;dfs1(1,0);
	dec();dfs2(half,0);
	printf("%u\n",ans);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值