• [总结]折半搜索



    折半搜索也是深搜的一种,当搜索的规模太大的时候,我们可以考虑将一次搜索分为两次小规模的搜索,再将这两次搜索的答案合并起来就能产生随后的答案。
    下面结合例题讲解:

    例题:P5194 [USACO05DEC]Scales 天平

    分析:
    题目范围(nleq 1000),由于题目规定“这一行中从第3个砝码开始,每个砝码的质量至少等于前面两个砝码的质量的和”,我们可以估计出实际(nleq 30)
    这个数据范围可以使用搜索,但实际上单纯的搜索会爆掉。因此我们采用折半搜索,将这n个砝码分为两部分搜索,并求出两部分的砝码能拼凑出的所有整数。接下来在第二部分中用二分法快速找出与第一部分的和最接近c的数,并把这个数作为答案,此后不断更新答案即可。

    代码如下:

    #include<bits/stdc++.h>
    #define N 1100010
    #define INF 2147483647
    using namespace std;
    int n,c,cnt1,cnt2;
    int sum1[N],sum2[N];
    int w[N];
    void DFS_fir(int dep,int sum){//第一次搜索
    	if(sum>c) return;
    	if(dep>n/2){//出口
    		if(sum<c) sum1[++cnt1]=sum;//计入答案
    		if(sum==c){//直接可以凑出最大重量
    			printf("%d",c);exit(0);
    		}
    		return;
    	}
    	DFS_fir(dep+1,sum+w[dep]);//取这个砝码
    	DFS_fir(dep+1,sum);//不取
    }
    void DFS_sec(int dep,int sum){//第二次搜索,同上
    	if(sum>c) return;
    	if(dep>n)
    	{
    		if(sum<c) sum2[++cnt2]=sum;
    		if(sum==c){
    			printf("%d",c);exit(0);
    		}
    		return;
    	}
    	DFS_sec(dep+1,sum+w[dep]);
    	DFS_sec(dep+1,sum);
    }
    int main()
    {
    	int wei,i;
    	scanf("%d%d",&n,&c);
    	for(i=1;i<=n;i++){
    		scanf("%d",&wei);
    		w[i]=wei;
    		if(wei>=c) break;//超过天平的承载能力,没有必要继续存入
    	}
    	n=i;
    	DFS_fir(1,0);
    	DFS_sec(n/2+1,0);
    	int ans=-INF;
    	sort(sum2+1,sum2+cnt2+1);//排序保证二分性
    	for(int i=1;i<=cnt1;i++){
    		int pos=upper_bound(sum2+1,sum2+cnt2+1,c-sum1[i])-sum2;//找到第一个大于c-sum1[i]的位置
            ans=max(ans,sum1[i]+sum2[pos-1]);//pos-1一定会保证sum1[i]+sum2[pos-1]的值小于等于c
            //使用lower_bound会寻找第一个大于等于c-sum1[i]的位置,如果找到了等于这个值的数,那么这个pos-1必然不是最优情况,因此使用upper_bound。
    	}
    	printf("%d",ans);
    	return 0;
    }
    

    例2:P4799 [CEOI2015 Day2]世界冰球锦标赛

    分析:
    当我们使用搜索解决这道题时,我们会发现传统的搜索方式无法满足本题N<=40(即产生(2^{40})个搜索结果)的数据范围。

    因此,本题使用折半搜索算法,将这N个数分为1~n/2n/2+1~n两组,并分别进行搜索处理,得到每组内元素可以组合成的数字。随后,我们令任意一组结果有序(这里为第二组),依次扫描第一组元素并不断累计由该元素和第二组元素组成的不超过M的数的个数,最终累计后的结果就是本题的答案。

    代码如下:

    #include<bits/stdc++.h>
    #define N 1100000
    #define ll long long
    using namespace std;
    int n,cnt1,cnt2;
    ll sum1[N],sum2[N];
    ll w[N],ans=0,c;
    void DFS_fir(int dep,ll sum){
    	if(sum>c) return;
    	if(dep>n/2){
    		if(sum<=c) sum1[++cnt1]=sum;
    		return;
    	}
    	DFS_fir(dep+1,sum+w[dep]);
    	DFS_fir(dep+1,sum);
    }
    void DFS_sec(int dep,ll sum){
    	if(sum>c) return;
    	if(dep>n)
    	{
    		if(sum<=c) sum2[++cnt2]=sum;
    		return;
    	}
    	DFS_sec(dep+1,sum+w[dep]);
    	DFS_sec(dep+1,sum);
    }
    int main()
    {
    	scanf("%d%lld",&n,&c);
    	for(int i=1;i<=n;i++) scanf("%lld",&w[i]);
    	DFS_fir(1,0);
    	DFS_sec(n/2+1,0);
    	sort(sum2+1,sum2+cnt2+1);
    	for(int i=1;i<=cnt1;i++)
    		ans+=upper_bound(sum2+1,sum2+cnt2+1,c-sum1[i])-sum2-1;
    	printf("%lld",ans);
    	return 0;
    }
    
  • 相关阅读:
    城市生态规划关键技术方法之三:城市生态系统服务功能理论与方法
    AE中用矢量数据剪裁栅格
    城市生态规划的基本原理与程序
    城市生态规划关键技术方法之一:生态系统健康理论与方法
    城市生态规划的基本概念、内容与方法
    终于找到使用Sql Server Management Studio导致蓝屏的罪魁祸首了
    保证相同类型的MDI子窗体只会被打开一次的方法
    甩掉数据字典,让Sql Server数据库也来一个自描述
    新鲜试用IE8 beta2
    让我目瞪口呆的program.exe
  • 原文地址:https://www.cnblogs.com/cyanigence-oi/p/11755532.html
Copyright © 2020-2023  润新知