折半搜索——另类的二分法


折半搜索

前置知识

笼统的二分
\(dfs\)
清醒的头脑

思路

对于一些很暴力的爆搜,我们发现如果使用通常的搜索会炸。会发现,我们通常的搜索有很多状态是冗余的根本不可能实现的,但是又不得不去搜索,所以说,整体二分可以使这些无法实现的冗余状态少搜一点。

\(\color{Fuchsia}{同时,整体二分之后,对于之后的更快的搜索才是更重要的,而不是整体二分的过程。}\)

网上找到一个好图

典型例题

传送门

洛谷 P4799 [CEOI2015 Day2]世界冰球锦标赛

作为一道普及组入门题,我也觉得不过分,但是再看一眼数据 \(N \leq 40\) ,众所周知,这题普通的爆搜时间复杂度是 \(\mathbf{O}(2^N)\) ,明显是不行的,那么应该怎么做呢?

他这个题是要求,选出一些数,然后他们的总和不可以超过 \(M\) ,挺套路的二分。

我现在把他分成两堆数,比如说样例的 \(100\quad1500\quad500\quad500\quad1000\)

现在就变成了

\(100\quad1500\)
\(500\quad500\quad1000\)

那分成这样有什么用呢?
我现在对每一堆数据分别做 \(\mathbf{O}(2^N)\) 的爆搜,然后找出他们可以组成多少钱的门票。
对于第二堆数排序。
然后对第一堆数一个一个的分析,二分在第二堆数中找到恰好合法的那个地方,前面的必定全部合法,那么这题就挺简单了啦。

#include 
#define debug puts("I ak IOI several times");
#define pb push_back
#define int long long
using namespace std;
template inline void read(T& t){
    t=0; register char ch=getchar(); register int fflag=1;
    while(!('0'<=ch&&ch<='9')){if(ch=='-') fflag=-1;ch=getchar();}
    while(('0'<=ch&&ch<='9')){t=((t<<1)+(t<<3))+ch-'0'; ch=getchar();} t*=fflag;
}
template  inline void read(T& t, Args&... args){
    read(t);read(args...);
}
template inline void write(T x){
    if(x<0) putchar('-'),x=~(x-1); int s[40],top=0;
    while(x) s[++top]=x%10,x/=10; if(!top) s[++top]=0;
    while(top) putchar(s[top--]+'0');
}
long long n,a[50],b[50],na,nb;
long long posa[1<<21],posb[1<<21],m;
long long cnta,cntb;
void dfs1(long long dep,long long sum){
	if(dep>na){
		posa[++cnta]=sum;
		return;
	}
	dfs1(dep+1,sum+a[dep]);
	dfs1(dep+1,sum);
}
void dfs2(long long dep,long long sum){
	if(dep>nb){
		posb[++cntb]=sum;
		return;
	}
	dfs2(dep+1,sum+b[dep]);
	dfs2(dep+1,sum);
}
#undef int
int main(){
	
#define int long long
	read(n,m);
	na=(n+1)/2;nb=n/2;
	for(int i=1;i<=na;++i) read(a[i]);
	for(int i=1;i<=nb;++i) read(b[i]);
	dfs1(1,0);
	dfs2(1,0);
	//sort(posa+1,posa+cnta+1);
	sort(posb+1,posb+cntb+1);
	int ans=0;
	for(int i=1;i<=cnta;++i)
		ans+=(upper_bound(posb+1,posb+cntb+1,m-posa[i])-posb-1);
	cout<