折半搜索
注明:引自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;
}
我不再把谁当成可靠的岸,河流就变成大海。

浙公网安备 33010602011771号