AcWing - 171 送礼物(双向dfs)

题目链接:点击查看

题目大意:给出n个礼物,以及最大承重量,问在最大承重量内所搬的礼物最重是多少

题目分析:因为每个礼物只有搬或不搬两种状态,所以我们可以直接搜索,但是题目给出的N最大到了46,直接搜的话时间复杂度到了2的46次方,肯定会T,那么我们该怎么优化呢,这里用到了双向搜索的技巧,先上定义:(摘自zx学长的ppt)

双向搜索:

除了迭代加深以外,双向搜索也可以避免在深层子树上浪费时间。在一些题目中,问题不但具有初态,还具有明确的终态,并且从初态开始搜索与从终态开始逆向搜索产生的搜索树都能覆盖整个状态空间。在这种情况下,就可以采取双向搜索——从初态和终态出发各搜索一半状态,产生两棵深度减半的搜索树,在中间交汇,组合成最终的答案。

 既然提到双向,我们就可以直接将n除以二来对待,这样一来时间复杂度瞬间下降到了2的23次方,差不多1e7不到,题目给了3秒可以应付,下面讲一下大体思路

首先我们可以将礼物折半分成两部分,用搜索先将第一部分的礼物可以组合的所有情况都搜出来,记录下来并去重,然后开始搜另一部分的礼物,每次搜到尽头时,用二分查找一下在不超过最大承重的范围内,所能和当前结果组成的最大结果是多少,并实时更新答案,总的时间复杂度为2^{n/2}+2^{n/2}*log(2^{n/2})=n*2^{n/2},就可以完美解决这个问题了

再来说一下几个可以优化剪枝的点吧:

  1. 一开始可以对礼物进行降序排序,排序后再折半搜索
  2. 因为第一次搜索的时间复杂度是2^{n/2},而第二次搜索多了个二分的时间开销,变为了2^{n/2}*log(2^{n/2}),所以我们可以适当增加第一次搜索的深度,从而减少第二次搜索的深度,经过多次提交测试出来第一次取值为(n/2)+2最为合适,比(n/2)的速度快了一倍

代码:

#include<iostream>
#include<cstdlib>
#include<string>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<climits>
#include<cmath>
#include<cctype>
#include<stack>
#include<queue>
#include<list>
#include<vector>
#include<set>
#include<map>
#include<sstream> 
using namespace std;

typedef long long LL;

const int inf=0x3f3f3f3f;

const int N=50;

LL n,w,ans;

int cnt,limit;

LL a[N],temp[(1<<25)+10];

void dfs1(int pos,LL sum)
{
	if(pos==limit)
	{
		temp[cnt++]=sum;
		return;
	}
	if(sum+a[pos]<=w)
		dfs1(pos+1,sum+a[pos]);
	dfs1(pos+1,sum);
}

void dfs2(int pos,LL sum)
{
	if(pos==n)
	{
		int t=lower_bound(temp,temp+cnt,-(w-sum))-temp;
		ans=max(ans,sum-temp[t]);
		return;
	}
	if(sum+a[pos]<=w)
		dfs2(pos+1,sum+a[pos]);
	dfs2(pos+1,sum);
}

bool cmp(const LL& a,const LL& b)
{
	return a>b;
}

int main()
{
//  freopen("input.txt","r",stdin);
    while(scanf("%lld%lld",&w,&n)!=EOF)
    {
    	for(int i=0;i<n;i++)
    		scanf("%lld",a+i);
    	sort(a,a+n,cmp);
    	limit=n/2+2;//选择适当的折半点,算是一种剪枝
    	cnt=0;
    	dfs1(0,0);
    	cnt=unique(temp,temp+cnt)-temp;
    	for(int i=0;i<cnt;i++)
    		temp[i]=-temp[i];
    	sort(temp,temp+cnt);
    	temp[cnt]=0;
    	ans=0;
    	dfs2(limit,0);
    	printf("%lld\n",ans);
    }
    
    
    

    
    
    
    
    
    
    
    
    
    

    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Frozen_Guardian

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值