折半搜索学习笔记

1. 思想

先搜前一半的状态,再搜后一半状态,最后记录两边状态并结合求解答案

通常,暴力搜索的时间复杂度是\(O(2^n)\),但折半搜索可以将时间复杂度降为\(O(2^{\frac{n}{2}})\),加上统计答案的时间复杂度,时间几乎为原来的一半

如何判断一道题是否用折半搜索,一般情况下,n较小,答案较大

如果将搜索过程看做一张图,该算法则用于求图上A到B,长度为L的路径条数

2. 例题

2.1. 世界冰球锦标赛

分别对前20个和后20个求值,然后对前一半的统计结果排序,最后二分求解即可

代码:

#include<cstdio>
#include<algorithm>
#define ll long long
using namespace std;
int n,l[2],idx[2];
ll m,a[2][45],ans[2][1048600],f[1048600];
void dfs(int id,int x,ll val)
{
	if(val>m) return;
	if(x>l[id])
	{
		ans[id][++idx[id]]=val;
		return; 
	}
	dfs(id,x+1,val+a[id][x]);
	dfs(id,x+1,val);
}
int main()
{
	scanf("%d%lld",&n,&m);
	for(int i=1;i<=n;i++)
	{
		scanf("%lld",&a[0][i]);
	}
	l[0]=n/2;
	l[1]=n-l[0];
	for(int i=l[0]+1,j=1;i<=n;i++)
	{
		a[1][j]=a[0][i];
		j++;
	}
	dfs(0,1,0);
	dfs(1,1,0);
	for(int i=1;i<=idx[0];i++)
	{
		f[i]=ans[0][i];
	}
	sort(f+1,f+idx[0]+1);
	ll res=0;
	for(int i=1;i<=idx[1];i++)
	{
		ll tmp=m-ans[1][i];
		int x=upper_bound(f+1,f+idx[0],tmp)-f;
		if(f[x]>tmp) x--;
	//	printf("%d %d\n",x,ans[1][i]);
		res=res+x;
	}
	printf("%lld",res);
	return 0;
}
2.2. [USACO12OPEN] Balanced Cow Subsets G

本题难点在于合并和去重

首先,可以分为在第一组,在第二组和不放三种情况,时间复杂度\(O(3^n)\)

考虑优化,可以通过折半搜索降低复杂度,即两边分别求解再合并

可以发现,如果两边的分法能结合,记两组分别为A,B,则必然存在\(A_1+A_2=B_1+B_2\)

即:\(A_1-B_1=B_2-A_2\)可以直接计算

接着考虑如何去重,利用状压的思想,可以用二进制记录是否取,最后统计答案即可

代码:

#include<cstdio>
#include<algorithm>
#include<vector>
#include<map>
#define ll long long
using namespace std;
int n,a[25],b[25],l1,l2,idx;
bool state[1048600];
map<ll,int> mp;
vector<int>v[60005];
void dfs1(int id,ll sum,int st)
{
	if(id>l1)
	{
		if(!mp[sum]) mp[sum]=++idx;
		v[mp[sum]].push_back(st);
		return;
	}
	dfs1(id+1,sum+a[id],st<<1|1);
	dfs1(id+1,sum-a[id],st<<1|1);
	dfs1(id+1,sum,st<<1);
}
void dfs2(int id,ll sum,int st)
{
	if(id>l2)
	{
		int x=mp[sum];
		for(int i=0;i<v[x].size();i++)
		{
			state[(v[x][i]<<l2)|st]=1;
		}
		return ;
	}
	dfs2(id+1,sum+b[id],st<<1|1);
	dfs2(id+1,sum-b[id],st<<1|1);
	dfs2(id+1,sum,st<<1);
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
	}
	l1=n/2,l2=n-l1;
	for(int i=l1+1,j=1;i<=n;i++)
	{
		b[j]=a[i];
		j++;
	}
	dfs1(1,0,0);
	dfs2(1,0,0);
	int ans=0;
	for(int i=1;i<(1<<n);i++) ans+=state[i];
	printf("%d",ans);
	return 0;
}
[USACO09NOV] Lights G

可以枚举每个节点的状态,然后取最小值,时间复杂度\(O(2^n)\),显然会T

考虑优化,可以从中间拆开,分别搜索,将结果压成二进制数

前一半用map存储,后一半与1<<n-1异或后直接统计即可

注意细节和long long,数组最好都开成40,不然会寄的很惨!!!

代码:

#include<cstdio>
#include<map>
#include<algorithm>
#define ll long long
using namespace std;
int n,m,head[40],edgenum;
struct edge{
	int to,nxt;
}e[1205];
void add_edge(int u,int v)
{
	e[++edgenum].nxt=head[u];
	e[edgenum].to=v;
	head[u]=edgenum;
}
int l1,l2;
bool st1[40],st2[40],ed1[40],ed2[40];
map<ll,int>mp;
ll get_result1()
{
	ll state=0;
	for(int i=1;i<=n;i++)
	{
		ed1[i]=st1[i];
		for(int j=head[i];j;j=e[j].nxt)
		{
			ed1[i]^=st1[e[j].to];
		}
	}
	for(int i=1;i<=n;i++)
	{
		state=(state<<1)+ed1[i];
	}
	return state;
}
ll get_result2()
{
	ll state=0;
	for(int i=1;i<=n;i++)
	{
		ed2[i]=st2[i];
		for(int j=head[i];j;j=e[j].nxt)
		{
			ed2[i]^=st2[e[j].to];
		}
	}
	for(int i=1;i<=n;i++)
	{
		state=(state<<1)+ed2[i];
	}
	return state;
}
void dfs1(int x,int y)
{
	if(x>l1)
	{
		ll state=get_result1();
	//	printf("%lld\n",state);
		if(!mp[state]) mp[state]=y+1;
		else mp[state]=min(mp[state],y+1);
		return;
	}
	st1[x]=1;
	dfs1(x+1,y+1);
	st1[x]=0;
	dfs1(x+1,y);
}
int ans=1e9;
ll cnt;
void dfs2(int x,int y)
{
	if(x>n)
	{
		ll state=get_result2();
		ll tmp=cnt^state;
		//printf("%lld\n",state); 
		if(mp[tmp])
		ans=min(ans,mp[tmp]+y-1);
		return;
	}
	st2[x]=1;
	dfs2(x+1,y+1);
	st2[x]=0;
	dfs2(x+1,y);
}
int main()
{
	scanf("%d%d",&n,&m);
	cnt=1ll*(1ll<<n)-1;
	for(int i=1;i<=m;i++)
	{
		int u,v;
		scanf("%d%d",&u,&v);
		add_edge(u,v);
		add_edge(v,u);
	}
	l1=n/2;
	dfs1(1,0);
	dfs2(l1+1,0);
	printf("%d",ans);
	return 0;
}
posted @ 2024-03-31 21:31  wangsiqi2010916  阅读(14)  评论(0编辑  收藏  举报