折半搜索(meet in the middle)

介绍

折半搜索,又称 meet in the middle \text{meet in the middle} meet in the middle,指将整个搜索过程分为两部分,并对两部分分别进行搜索,最后得到两个答案序列,将这两个答案序列进行合并,即可得到最终的答案。

这样做的目的是降低时间复杂度。举个例子,如果每层搜索都有两种选择,那么时间复杂度是 O ( 2 n ) O(2^n) O(2n)的。如果我们用折半搜索,那时间复杂度就降为 O ( 2 n / 2 + k ) O(2^{n/2}+k) O(2n/2+k),其中 k k k指将两个答案序列合并的时间复杂度。


例题

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

题目大意

n n n场比赛,第 i i i场比赛的门票的价格为 a i a_i ai Bobek \text{Bobek} Bobek m m m元钱,问他有多少种不同的观赛方案。

1 ≤ n ≤ 40 , 1 ≤ m ≤ 1 0 18 , 1 ≤ a i ≤ 1 0 16 1\leq n\leq 40,1\leq m\leq 10^{18},1\leq a_i\leq 10^{16} 1n40,1m1018,1ai1016

题解

我们首先可以想到的是用状压枚举每一种情况,但这样的时间复杂度为 O ( 2 n ) O(2^n) O(2n),会 TLE \text{TLE} TLE

我们考虑用折半搜索解决问题。

先将所有比赛分为两部分,分别求出两个部分中所有可能的观赛方案的花费。那么,我们在前一部分中取方案 a a a,后一部分中取方案 b b b,则只有满足方案 a a a和方案 b b b的花费之和小于等于 m m m,这两种方案才会对答产生贡献。

那么,我们用一个数组 w w w记录前一部分的每种方案的花费,然后将 w w w从小到大排序。对于后一部分的每种方案的花费 t t t,我们在 w w w中二分求所有满足花费小于等于 m − t m-t mt的观赛方案数量,再将其贡献在答案中即可。

求出 w w w并排序的时间复杂度为 O ( n 2 n / 2 ) O(n2^{n/2}) O(n2n/2),求出每个 t t t并二分查找的时间复杂度为 O ( n 2 n / 2 ) O(n2^{n/2}) O(n2n/2),所以总时间复杂度为 O ( n 2 n / 2 ) O(n2^{n/2}) O(n2n/2)

code

#include
using namespace std;
int n,w1=0;
long long m,now,ans=0,a[45],w[1<<20];
int main()
{
	scanf("%d%lld",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%lld",&a[i]);
	}
	for(int s=0;s<1<<(n/2);s++){
		w[++w1]=0;
		for(int i=1;i<=n/2;i++){
			if((s>>i-1)&1) w[w1]+=a[i];
		}
	}
	sort(w+1,w+w1+1);
	for(int s=0;s<1<<(n-n/2);s++){
		now=0;
		for(int i=1;i<=n-n/2;i++){
			if((s>>i-1)&1) now+=a[n/2+i];
		}
		ans+=upper_bound(w+1,w+w1+1,m-now)-w-1;
	}
	printf("%lld",ans);
	return 0;
}

你可能感兴趣的:(算法,算法)