c++ 折半搜索

一、引入

在之前的学习中,我们已经学习了很多种搜索方法。让我们先看一道题:

题目描述

今年的世界冰球锦标赛在捷克举行。Bobek 已经抵达布拉格,他不是任何团队的粉丝,也没有时间观念。他只是单纯的想去看几场比赛。如果他有足够的钱,他会去看所有的比赛。不幸的是,他的财产十分有限,他决定把所有财产都用来买门票。

给出 Bobek 的预算和每场比赛的票价,试求:如果总票价不超过预算,他有多少种观赛方案。如果存在以其中一种方案观看某场比赛而另一种方案不观看,则认为这两种方案不同。

输入格式

第一行,两个正整数 N N N M ( 1 ≤ N ≤ 40 , 1 ≤ M ≤ 1 0 18 ) M(1 \leq N \leq 40,1 \leq M \leq 10^{18}) M(1N40,1M1018),表示比赛的个数和 Bobek 那家徒四壁的财产。
第二行, N N N 个以空格分隔的正整数,均不超过 1 0 16 10^{16} 1016,代表每场比赛门票的价格。

输出格式

输出一行,表示方案的个数。由于 N N N 十分大,注意:答案 ≤ 2 40 \le 2^{40} 240

首先,尝试简单的深搜,其代码如下:

#include<bits/stdc++.h>
#define ll long long
#define bug printf("---OK---")
#define pa printf("A: ")
#define pr printf("\n")
#define pi acos(-1.0)
#define INF 0x3f3f3f3f3f3f3f3f
using namespace std;
ll n,m,a[42];
ll cnt;
void dfs(ll k,ll sum){
	if(sum>m){
		return;
	}
	if(k==n+1){
		cnt++;
		return;
	}
	dfs(k+1,sum+a[k]);
	dfs(k+1,sum);
}
int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	dfs(1,0);
	cout<<cnt;
	return 0;
}

但是……

在这里插入图片描述
这时,就需要另外一种强大的方法——折半搜索。

二、介绍

1 概念

折半搜索主要思想是将整个搜索过程分成两半,分别搜索,最后将两半的结果合并。这样,原本需要进行的指数级别的搜索,其复杂度的指数可以减半,即让复杂度从 O ( 2 n ) O(2^n) O(2n) 降到 O ( 2 n / 2 ) O(2^{n/2}) O(2n/2)

2 回到题目

2.1 思路

我们可以将所有的比赛分为两部分,然后分别对这两部分进行搜索,得到两个答案序列,再将这两个答案序列进行合并,即可得到最终的答案。

2.2 举例

假设我们有 4 场比赛,票价分别为 1, 2, 3, 4 元,Bobek 有 5 元钱。
我们可以将比赛分为两部分,第一部分包含前两场比赛,第二部分包含后两场比赛。然后我们分别对这两部分进行搜索,得到两个答案序列:

  • 第一部分的答案序列(数组a)为:0, 1, 2, 3(表示不买票,只买第一场的票,只买第二场的票,两场都买)
  • 第二部分的答案序列(数组b)为:0, 3, 4, 7

然后我们将这两个答案序列进行合并,得到所有可能的观赛方案的总票价:0, 1, 2, 3, 3, 4, 5, 6, 4, 5, 6, 7, 7, 8, 9, 10。
最后,我们只需要统计总票价不超过 5 元的观赛方案的数量,即可得到最终的答案。
在实际操作中,我们可以在第二遍dfs时,使用二分直接累加结果。

2.3 代码

#include<bits/stdc++.h>
#define ll long long
#define bug printf("---OK---")
#define pa printf("A: ")
#define pr printf("\n")
#define pi acos(-1.0)
#define INF 0x3f3f3f3f3f3f3f3f
using namespace std;
ll n,m,a[42],b[1500002];
ll ans,cnt;
void dfs(ll k,ll lim,ll sum,ll flag){
	if(sum>m){
		return;
	}
	if(k>lim){
		if(flag==0){
			b[++cnt]=sum;
		}
		else{
			ll k=upper_bound(b+1,b+1+cnt,m-sum)-b;
			ans+=k-1;
		}
		return;
	}
	dfs(k+1,lim,sum,flag);
	dfs(k+1,lim,sum+a[k],flag);
}
int main(){
	cin>>n>>m;
	int i,j;
	for(i=1;i<=n;i++){
		cin>>a[i];
	}
	dfs(1,n/2,0,0);
	sort(b+1,b+1+cnt);
	dfs(n/2+1,n,0,1);
	cout<<ans;
	return 0;
}

三、实践

1 XOR

1.1 题目描述

给出一个 n × m 的网格,每个格子上有权值 a[i][j],现在zhqwq要从 (1,1) 走到 (n,m)去抱走wyz,每次只能向右或向下走,
沿路计算异或和,求异或和等于 k 的路径数。

1.2 思路

开一个 m a p map map ,其中, m p [ i ] [ j ] mp[i][j] mp[i][j] 表示坐标,其映射值为走到此处的步数。
采用折半搜索。第一遍dfs记录方案,第二遍累计答案 m p [ x ] [ y ] [ s u m ⊕ k ⊕ a [ x ] [ y ] ] mp[x][y][sum\oplus k\oplus a[x][y]] mp[x][y][sumka[x][y]]

1.3 代码

#include<bits/stdc++.h>
#define ll long long
#define bug printf("---OK---")
#define pa printf("A: ")
#define pr printf("\n")
#define pi acos(-1.0)
#define INF 0x3f3f3f3f3f3f3f3f
using namespace std;
ll n,m,k,a[22][22];
map<ll,ll> mp[22][22];
ll ans,cnt;
void dfs1(ll x,ll y,ll sum){
	if(x>n||y>m){
		return;
	}
	if(x+y==(n+m)/2+1){
		++mp[x][y][sum];
		return;
	}
	dfs1(x+1,y,a[x+1][y]^sum);
	dfs1(x,y+1,a[x][y+1]^sum);
}
void dfs2(ll x,ll y,ll sum){
	if(x<1||y<1){
		return;
	}
	if(x+y==(n+m)/2+1){
		ans+=mp[x][y][sum^k^a[x][y]];
		return;
	}
	dfs2(x-1,y,a[x-1][y]^sum);
	dfs2(x,y-1,a[x][y-1]^sum);
}
int main(){
	cin>>n>>m>>k;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			cin>>a[i][j];
		}
	}
	dfs1(1,1,a[1][1]);
	dfs2(n,m,a[n][m]);
	cout<<ans;
	return 0;
}
  • 20
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值