双向搜索&meet in the middle
定义:
从状态图的起点和终点同时进行广搜或深搜
作用:
其实不知道啊,或许可以在一些无法寻找性质以优化求最优解的题目中以某些形式替代状压
例如:对于 \(n>30\) 的题目状压的时间复杂度就来到了 \(O(2^{n})\) 这是不足以通过 \(1e9\) 的,但是折半搜索就将时间缩短到 \(O(2^{\frac{n}{2}} log 2^{\frac{n}{2}})\) 即为 \(O(2^{\frac{n}{2}}n)\)
但这种取代是有限制的,必须有一种关系能使前半段与后半段产生联系——否则便无法将前后合并,通常这种题的难点也在于合并前后答案
过程:
看得懂 \(OI Wiki\) 的神人来

所以我们看道例题吧P4799 CEOI 2015] 世界冰球锦标赛 (Day2) - 洛谷
题意
给定 \(n\) 个数和最大消费,每个数都有一个价值,求价值和不超过最大消费的所有序列
做法
首先观察数据范围 \(n\mathrm{} \le 40\),像这样的数据范围一般都是人类智慧(比如这道题就是我们的折半搜索)
将我们的 \(n\) 分为 \(1\) 到 \(n/2\) 和 \(n/2+1\) 到 \(n\) 两部分,分别对这两部分进行 \(dfs\) ,用两个数组 \(a,b\)统计搜索状态,我们有
int suma[N],sumb[N];
int cnta,cntb;
inline void dfs(int l,int r,int sum,int a[],int &cnt){
if(sum>M) return ;
if(l>r){
a[++cnt]=sum;
return ;
}
dfs(l+1,r,sum+w[l],a,cnt);
dfs(l+1,r,sum,a,cnt);
return ;
}
int midn=n/2;
dfs(1,mid,0,suma,cnta);
dfs(mid+1,n,0,sumb,cntb);
这样我们就得到了前后的搜索状态,考虑如何合并:
先进行排序方便我们对序列操作,我们发现,对于前后的两个序列,只要两个状态的和为 \(0\) 那么这个合并就是合法的,对于一个序列,它的状态数最多有 \(2^{20}\) 种,那么我们很容易想到枚举其中一个序列的全都状态,然后二分另一个序列,那么这道题就愉快的完成了
#include<bits/stdc++.h>
using namespace std;
const int N=1<<21;
int suma[N],sumb[N];
int cnta,cntb;
int w[50];
int n,M;
inline void dfs(int l,int r,int sum,int a[],int &cnt){
if(sum>M) return ;
if(l>r){
a[++cnt]=sum;
return ;
}
dfs(l+1,r,sum+w[l],a,cnt);
dfs(l+1,r,sum,a,cnt);
return ;
}
int ans;
int main(){
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cin>>n>>M;
for(int i=1;i<=n;i++){
cin>>w[i];
}
int mid=n/2;
dfs(1,mid,0,suma,cnta);
dfs(mid+1,n,0,sumb,cntb);
sort(suma+1,suma+1+cnta);
for(int i=1;i<=cntb;i++){
ans+=upper_bound(suma+1,suma+1+cnta,M-sumb[i])-suma-1;
}
cout<<ans;
return 0;
}

浙公网安备 33010602011771号