【学习笔记】meet in middle

一、简介

折半搜索,即对于那些数据范围较小却又不能直接暴搜的题目,采取分两半暴搜后再想办法合并两部分的答案的办法。一般的方式是二分或者状压、map

二、例题

1.世界冰球锦标赛

对前一半和后一半分别进行暴搜,将前半部分的答案存起来并排序,对于每个后面的答案在这个序列中二分即可。时间复杂度 \(O(2^{\frac{n}{2}}n)\)

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define uprb upper_bound
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=(1<<20)+5;
int n,tot;
ll m,a[45],hp[maxn],ans;
il void dfs1(int x,ll res){
//	cout<<x<<" "<<res<<"\n";
	if(x>n>>1){
		hp[++tot]=res;
		return ;
	}
	dfs1(x+1,res);
	dfs1(x+1,res+a[x]);
}
il void dfs2(int x,ll res){
	if(x>n){
//		cout<<res<<" "<<uprb(hp+1,hp+tot+1,m-res)-hp-1<<"\n";
		ans+=uprb(hp+1,hp+tot+1,m-res)-hp-1;
		return ;
	}
	dfs2(x+1,res);
	dfs2(x+1,res+a[x]);
}
namespace cplx{
	bool end;
	il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	dfs1(1,0);
	sort(hp+1,hp+tot+1);
	dfs2((n>>1)+1,0);
	cout<<ans;
	return 0;
}
}
int main(){return asbt::main();}

2.[NOI2001] 方程的解数

按照之前的套路,我们首先枚举前三个未知数,存到 map 里,再枚举后面的未知数在对应的位置查询即可。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
int n,m,a[10],b[10],ans;
map<int,int> hp;
il int qpow(int x,int y){
	int res=1;
	while(y){
		if(y&1){
			res*=x;
		}
		x*=x,y>>=1;
	}
	return res;
}
il void dfs1(int x,int res){
	if(x>n>>1){
		hp[res]++;
		return ;
	}
	for(int i=1;i<=m;i++){
		dfs1(x+1,res+a[x]*qpow(i,b[x]));
	}
}
il void dfs2(int x,int res){
	if(x>n){
		ans+=hp[-res];
		return ;
	}
	for(int i=1;i<=m;i++){
		dfs2(x+1,res+a[x]*qpow(i,b[x]));
	}
}
namespace cplx{
	bool end;
	il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		cin>>a[i]>>b[i];
	}
	dfs1(1,0);
	dfs2((n>>1)+1,0);
	cout<<ans;
	return 0;
}
}
int main(){return asbt::main();}

3.[USACO12OPEN] Balanced Cow Subsets G

依然是老套路,先搜前一半,再搜后一半,存储两个集合之差,判断每一种选法是否成立。需要状压。

Code
#include<bits/stdc++.h>
#define int long long
#define il inline
#define pb push_back
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=(1<<20)+5,maxm=6e4+5;
int n,a[25],cnt;
bool vis[maxn];
map<int,int> hao;
vector<int> cun[maxm];
il void dfs1(int x,int sta,int res){
	if(x>n>>1){
		if(!hao.count(res)){
			hao[res]=++cnt;
		}
		int p=hao[res];
		cun[p].pb(sta);
		return ;
	}
	dfs1(x+1,sta,res);
	dfs1(x+1,sta|1<<(x-1),res+a[x]);
	dfs1(x+1,sta|1<<(x-1),res-a[x]);
}
il void dfs2(int x,int sta,int res){
	if(x>n){
		if(hao.count(res)){
			for(int S:cun[hao[res]]){
				vis[sta|S]=1;
			}
		}
		return ;
	}
	dfs2(x+1,sta,res);
	dfs2(x+1,sta|1<<(x-1),res+a[x]);
	dfs2(x+1,sta|1<<(x-1),res-a[x]);
}
namespace cplx{
	bool end;
	il double usdmem(){return (&begin-&end)/1048576.0;}
}
signed main(){
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	dfs1(1,0,0);
	dfs2(n/2+1,0,0);
	int ans=0;
	for(int i=1;i<1<<n;i++){
		ans+=vis[i];
	}
	cout<<ans;
	return 0;
}
}
signed main(){return asbt::main();}

4.[USACO09NOV] Lights G

对于每个点,状压记录它会影响的点。折半搜时用 map 存答案即可。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=40;
int n,m,ans=1e9;
ll sta[maxn];
map<ll,int> hua;
il void dfs1(int x,int res,ll S){
	if(x>n>>1){
		if(!hua.count(S)){
			hua[S]=res;
		}
		else if(hua[S]>res){
			hua[S]=res;
		}
		return ;
	}
	dfs1(x+1,res+1,S^sta[x]);
	dfs1(x+1,res,S);
}
il void dfs2(int x,int res,ll S){
	if(x>n){
		ll nS=((1ll<<n)-1)^S;
		if(hua.count(nS)){
			ans=min(ans,hua[nS]+res);
		}
		return ;
	}
	dfs2(x+1,res+1,S^sta[x]);
	dfs2(x+1,res,S);
}
namespace cplx{
	bool end;
	il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		sta[i]|=1ll<<(i-1);
	}
	for(int i=1,u,v;i<=m;i++){
		cin>>u>>v;
		sta[u]|=1ll<<(v-1);
		sta[v]|=1ll<<(u-1);
	}
	dfs1(1,0,0);
	dfs2(n/2+1,0,0);
	cout<<ans;
	return 0;
}
}
int main(){return asbt::main();}

5.[abc184_f]Programming Contest

依然是熟悉的套路。排序后二分即可。注意后一半如果超过了限制就不能二分了。

Code
#include<bits/stdc++.h>
#define int long long
#define il inline
#define uprb upper_bound
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=(1<<20)+5;
int n,m,a[45],hp[maxn],cnt,ans;
il void dfs1(int x,int res){
	if(x>n>>1){
		hp[++cnt]=res;
		return ;
	}
	dfs1(x+1,res);
	dfs1(x+1,res+a[x]);
}
il void dfs2(int x,int res){
	if(res>m){
		return ;
	}
	if(x>n){
		ans=max(ans,res+*(uprb(hp+1,hp+cnt+1,m-res)-1));
		return ;
	}
	dfs2(x+1,res);
	dfs2(x+1,res+a[x]);
}
namespace cplx{
	bool end;
	il double usdmem(){return (&begin-&end)/1048576.0;}
}
signed main(){
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	dfs1(1,0);
	sort(hp+1,hp+cnt+1);
	dfs2(n/2+1,0);
	cout<<ans;
	return 0;
}
}
signed main(){return asbt::main();}
posted @ 2025-05-03 16:13  zhangxy__hp  阅读(17)  评论(0)    收藏  举报