题目
作为惩罚,GY被遣送去帮助某神牛给女生送礼物(GY:貌似是个好差事)但是在GY看到礼物之后,他就不这么认为了。某神牛有N个礼物,且异常沉重,但是GY的力气也异常的大(-_-b),他一次可以搬动重量和在w(w<=2^31-1)以下的任意多个物品。GY希望一次搬掉尽量重的一些物品,请你告诉他在他的力气范围内一次性能搬动的最大重量是多少。
题解
双向搜索
如果从一个方向搜索,会不断的产生分支,搜索树呈△,在深层子树上浪费的时间相当多。
如果可以从两个方向同时开始搜索,那么每次搜索的任务只有一半,搜索树呈◇。
双向搜索把两棵搜索树合并在一起,产生两棵深度减半的搜索树,在中间交汇出现答案。因为这样的可以搜索覆盖整个状态空间,故答案的正确性很有保障。
双向搜索需要题目给出明确的“初态”和“终态”,这题完全符合。
第一次搜索枚举用1~n/2号的物品能产生的重量。第二次枚举用n/2+1~n号物品能产生的重量。在中间交汇的地方,用过寻找前驱的方法,使得搜索2得到的重量+搜索1中的状态 最接近w。
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=60;
int w,n,ans=0;
int a[maxn];
bool cmp(int a1,int a2)
{
return a2>a1;
}
int m=0,b[(1<<23)+10];
long long now=0;
void dfs_1(int k,int ed)//1~mid
{
if(now>w) return ;
if(k>ed)
{
b[++m]=now;
return ;
}
dfs_1(k+1,ed);
now+=a[k];
dfs_1(k+1,ed);
now-=a[k];
}
void dfs_2(int k,int ed)
{
if(now>w) return ;
if(k>ed)//找前驱
{
if(w-now<b[1]) return ;
int tmp=upper_bound(b+1,b+m+1,w-now)-b-1;
tmp=now+b[tmp];
if(tmp>ans) ans=tmp;
return ;
}
dfs_2(k+1,ed);
now+=a[k];
dfs_2(k+1,ed);
now-=a[k];
}
int main()
{
scanf("%d%d",&w,&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
sort(a+1,a+n+1,cmp);
dfs_1(1,n/2);
sort(b+1,b+m+1);
dfs_2(n/2+1,n);
printf("%d\n",ans);
return 0;
}