题面描述
作为惩罚,GY被遣送去帮助某神牛给女生送礼物(GY:貌似是个好差事)但是在GY看到礼物之后,他就不这么认为了。某神牛有N个礼物,且异常沉重,但是GY的力气也异常的大(-_-b),他一次可以搬动重量和在
w
(
w
<
=
2
31
−
1
w(w<=2^{31}-1
w(w<=231−1)以下的任意多个物品。GY希望一次搬掉尽量重的一些物品,请你告诉他在他的力气范围内一次性能搬动的最大重量是多少。
输入格式
第一行两个整数,分别代表W和N。
以后N行,每行一个正整数表示
G
[
i
]
G[i]
G[i],
G
[
i
]
≤
2
31
−
1
G[i]≤ 2^{31}-1
G[i]≤231−1。
输出格式
仅一个整数,表示GY在他的力气范围内一次性能搬动的最大重量。
样例输入
20 5
7
5
4
18
1
样例输出
19
数据范围与约定
对于20%的数据
N
<
=
26
N<=26
N<=26
对于40%的数据
W
<
=
2
26
W<=2^{26}
W<=226
对于100%的数据
N
<
=
45
W
≤
2
31
−
1
N<=45~~~ W≤2^{31}-1
N<=45 W≤231−1
思路
观察一下,
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;
}