搜索<3>——折半搜索(meet in the middle)

本文介绍了折半搜索在两个经典算法题目中的应用,包括动态规划求解和状态重复的处理方法,展示了如何通过折半策略优化搜索过程并解决计数问题。
摘要由CSDN通过智能技术生成

我上网一搜折半搜索,结果跳出来的为什么全是二分??????????????????????????气死我了。

回归正题,折半搜索指将整个搜索过程分为两部分,并对两部分分别进行搜索,最后得到两个答案序列,将这两个答案序列进行合并,得到最终的答案。合并时,我们一般使用二分或尺取法合并,这一是meet in the middle最难的一部分。这样可以大大降低复杂度,比如原来\Theta ({2}^{^{n}})的这样就变成了\Theta (2^{\frac{n}{2}})。来看一些题吧。

P4799:

很经典的题目了。首先,可以状压枚举每种情况,但是\Theta (2^{n})TLE,所以考虑折半搜索。将前一半的搜索状态存入a数组,后一半存入b数组。难点主要在于最后答案的组合统计。我们可以现将a或b数组sort,然后通过枚举另一个数组中的状态,来实现统计答案。令pos为当前枚举的x的第一个x+a_{pos}>m,那么在pos之前的位置都可以贡献答案,所以ans+=pos-1。那么找pos的工程可以用upper_bound()实现。

#include <bits/stdc++.h>
using namespace std;
vector<int> l,r;
long long ticket[100];
long long n,m,mid;
void dfs1(long long step, long long cost){
    if(cost>m)
		return;
    if(step>mid){
	   l.push_back(cost); 
	   return;
	}
	dfs1(step+1,cost+ticket[step]);
    dfs1(step+1,cost);
}
void dfs2(long long step, long long cost){
    if(cost>m)
		return;
    if(step>n){
	   r.push_back(cost); 
	   return;
	}
	dfs2(step+1,cost+ticket[step]);
    dfs2(step+1,cost);
}
int main(){
    cin>>n>>m;
	mid=(n+1)>>1;
    for(long long i=1;i<=n;i++)
    	cin>>ticket[i];
	dfs1(1,0); 
	dfs2(mid+1,0);
	sort(r.begin(),r.end());
	long long ans=0;
	for(auto x:l)
	   ans+=upper_bound(r.begin(),r.end(),m-x)-r.begin();
	cout<<ans<<endl;
    return 0;
}

P3067:

很不显然,这题比世界冰球锦标赛难一些。

当然,上来考虑\Theta (3^n)(有3种状态:不放入任何集合,放入左边,放入右边)暴力,但显然大于1E9,你会得到一个完美的TLE。在搜索时我们可以0,1,-1来表示,像这样↓

dfs(dep+1,sum);
dfs(dep+1,sum+a[dep]);
dfs(dep+1,sum-a[dep]);

但是我们得到的答案可能会有重复,因为我们可能把一个数选入左集合或右集合,但是都加入了状态,所以我们需要判重。所以搜索时还要去记录状态,用一个vis数组判重。

if(!vis[a[l].sta|b[r].sta])
	vis[a[l].sta|b[r].sta]=1;//sta记录二进制的选数状态  1表示选 0表示没选

最后要统计答案,排序后双指针扫描一遍即可。

#include <bits/stdc++.h>
using namespace std;
int n,a[50],cntl,cntr,ans;
bool vis[1<<22];
struct node{
	int sta,val;
}l[1<<22],r[1<<22];
bool cmpl(node x,node y){
	return x.val<y.val;
}
bool cmpr(node x,node y){
	return x.val>y.val;
}
void dfs1(int x,int tot,int sta){
	if(x>(n>>1)){
		cntl++;
		l[cntl]=node{sta,tot};
		return;
	}
	dfs1(x+1,tot,sta);
	dfs1(x+1,tot+a[x],sta|(1<<(x-1)));
	dfs1(x+1,tot-a[x],sta|(1<<(x-1)));
}
void dfs2(int x,int tot,int sta){
	if(x>n){
		cntr++;
		r[cntr]=node{sta,tot};
		return;
	}
	dfs2(x+1,tot,sta);
	dfs2(x+1,tot+a[x],sta|(1<<(x-1)));
	dfs2(x+1,tot-a[x],sta|(1<<(x-1)));
}
int main(){
	cin>>n;
	for(int i=1;i<=n;i++)
        cin>>a[i];
	dfs1(1,0,0);
	dfs2((n>>1)+1,0,0);
	sort(l+1,l+cntl+1,cmpl);
	sort(r+1,r+cntr+1,cmpr);
	for(int i=1,j=1,pos;i<=cntl && j<=cntr;i++){
		while(j<=cntr && l[i].val+r[j].val>0)
            j++;
		for(pos=j;l[i].val==-r[pos].val;pos++){
			int sta=(l[i].sta|r[pos].sta);
			if(!vis[sta]){
                vis[sta]=1;
                ans++;
            }
		}
	}
	cout<<ans<<endl;
	return 0;
}

注意,最后别忘了把0的那种方案减去。(我没减是因为ans设为-1,已经被我改了)

以上就是本期的全部内容了,我们下期再见!

温馨提示:本期的全部代码都有问题,请不要无脑Ctrl C+Ctrl V

  • 4
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值