介绍
折半搜索,又称 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} 1≤n≤40,1≤m≤1018,1≤ai≤1016
题解
我们首先可以想到的是用状压枚举每一种情况,但这样的时间复杂度为 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 m−t的观赛方案数量,再将其贡献在答案中即可。
求出 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<bits/stdc++.h>
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;
}