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