折半搜索

注明:引自https://www.luogu.com.cn/article/pynrv17c

做法为将整个搜索的过程分为两部分,然后每部分分别进行搜索,最后将得到两个答案序列,再将答案序列进行合并,即可得到最终的答案。

可以发现,当状态非常之多的时候,这种优化还是非常明显的,最优情况下可以直接把复杂度开个根号。

需要注意的是,折半搜索应用的时候需要满足以下条件:

搜索各项不能相互干扰。

需要满足搜索状态可逆,就是说一个状态可以从两个方向都得到。

折半搜索其实还是用的比较广泛的。

BFS ,DFS 还有状压 DP 都有类似的应用。

折半搜索一般的难点就在于最后的答案序列合并。(可能会使用一些奇奇怪怪的高深的玩意才能搞得出来)

实现非常灵活,需要按照题目来进行选择。

一般比较常见的排序后二分,双指针还有哈希表(自然还有一些我没见过的)。

逐个分析一下:

排序后二分 复杂度肯定是带 log 的。

为什么正确?

在一个有序的序列中,如果我们可以找到一个位置可以做到对答案有贡献,那么这个位置之前的所有位置都是可以对答案有贡献的。

所以直接统计就好。

sort(a+1,a+1+cnta);
for(re int i=1;i<=cntb;i++)
    ans+=upper_bound(a+1,a+1+cnta,m-b[i])-a-1;

双指针一般是线性的,效果很不错,代码比较好写。

缺点就是比较难想,需要考虑一些问题(例如单调性)。

int l=cnta,r=1;
for(r=1;r<=cntb;r++)
{
	while(a[l]+b[r]>m)l--;//m是一个限制条件
	if(a[l]&&b[r]) ans+=(l-1);  
} 

哈希表也是线性,至于具体如何就要看脸了。

如果不被卡的话哈希表确实是一种非常不错的选择。

具体就是先处理一半,然后把搜到的答案存到哈希表里,然后搜另一半,之后再去哈希表里找,把结果合并就可以了。

void dfs1()//搜索一半 
{
	if (到达边界)
    {
		add(hash(x)); 
		return;
	} 
	... 
}

void dfs2()//处理另一半
{
	if (到达边界)
    {
		ans+=sum[hash(x)];
		return;
	} 
	... 
}

做题记录

1. [USACO12OPEN] Balanced Cow Subsets

难以想到的是如何求子集数量,所以想不出来如何搜。考虑折半搜,每一个a[i]可以归为3类:在第一组(前一半总和为a,后一半总和为c),在第二组(前一半总和为b,后一半总和为d),不选。搜索时记录所选的状态(n的范围只有20)。当a+b=c+d时(即a-c=d-b),就满足了划分和相等的条件,此时也可以很显然地推出所选的集合,从这一思路引申开来,把所有合法的集合求出来,算总数即可。

搜索部分
inline void ldfs(int x,int sum,int sum1,int st)
{
	if(x==n/2)
	{
		if(mp[sum-sum1]==0) mp[sum-sum1]=++tot;
		vec[mp[sum-sum1]].push_back(st);
		return ;
	}
	ldfs(x+1, sum+a[x+1], sum1, st|(1<<(x)));
	ldfs(x+1, sum, sum1+a[x+1], st|(1<<(x)));
	ldfs(x+1, sum, sum1, st);
}
inline void rdfs(int x,int sum,int sum1,int st)
{
	if(x==n)
	{
		int num=mp[sum1-sum];
		if(num)
		{
			for(int i=0;i<vec[num].size();i++)
			{
				f[vec[num][i]|st]=1;
			}	
		}
		return ;
	}
	rdfs(x+1, sum+a[x+1], sum1, st|(1<<(x)));
	rdfs(x+1, sum, sum1+a[x+1], st|(1<<(x)));
	rdfs(x+1, sum, sum1, st);
}

2. [USACO09NOV] Lights G

标签:高斯消元,很合理。

考虑将这个01状态变为异或,即第i个点为二进制下第i位,给他赋一个值为他和他所连的点的异或和,那么dfs遍历时异或一下这个值,如果最终异或出来的结果是(1<<n)-1就证明全部为1。然后就是折半搜索了。

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=40;
int n, m, mi[2000005], xorr[20000005], ans=1e9;
map<int,int> t;
void ldfs(int x,int res,int st)
{
	if(x==n/2)
	{
		if(st==mi[n]-1)
		{
			ans=min(ans, res);
			return ;
		}
		if(!t[st]||t[st]>res) t[st]=res;
		return ;
	}
	ldfs(x+1, res+1, st^xorr[x+1]);
	ldfs(x+1, res, st);
}
void rdfs(int x,int res,int st)
{
	if(x==n)
	{
		if(st==mi[n]-1)
		{
			ans=min(ans, res);
			return ;
		}
		if(t[(mi[n]-1)^st]) ans=min(ans, res+t[(mi[n]-1)^st]);
		return ;
	}
	rdfs(x+1, res+1, st^xorr[x+1]);
	rdfs(x+1, res, st);
}
signed main()
{
	cin>>n>>m;
	mi[0]=1;
	for(int i=1;i<=n;i++) mi[i]=mi[i-1]<<1;
	for(int i=1;i<=m;i++)
	{
		int u, v;
		cin>>u>>v;
		xorr[u]^=mi[v-1], xorr[v]^=mi[u-1];
		xorr[i]^=mi[i-1];
	}
	ldfs(0, 0, 0);
	rdfs(n/2, 0, 0);
	cout<<ans;
	return 0;
}

我不再把谁当成可靠的岸,河流就变成大海。

posted @ 2025-05-15 21:28  zhouyiran2011  阅读(45)  评论(0)    收藏  举报