二分查找算法

二分查找

引言

来,玩个游戏吧,在 1 1 1 1000 1000 1000 内,我想一个数你来猜,当你猜数时,我会告诉你大了还是小了,你需要几次才能猜中呢?

正确的答案是: 10 10 10 次必定可以猜出来

想学吗?开始吧!

二分查找的原理

二分查找通过每次将范围缩短一半的方法来获得优秀的时间复杂度 O ( log ⁡ 2 n ) O(\log_2 n) O(log2n)

比如查找在 1 1 1 50 50 50 以内的数字,我们选择的是数字 45 45 45 ,那么该怎么搜呢

  1. 初始范围 [ 1 , 50 ] [1,50] [1,50] ,我们取左右区间的平均值,也就是 25 25 25 25 25 25 小了,所以缩短左边界
  2. 范围变成了 [ 25 , 50 ] [25,50] [25,50] ,继续取左右区间平均值, 37.5 37.5 37.5 ,我们取整数 37 37 37 37 37 37 小了,所以缩短左边界
  3. 范围变成了 [ 37 , 50 ] [37,50] [37,50] ,重复操作,缩短左边界
  4. 范围变成了 [ 43 , 50 ] [43,50] [43,50]
  5. 范围变成了 [ 43 , 46 ] [43,46] [43,46]
  6. 范围变成了 [ 44 , 46 ] [44,46] [44,46]
  7. 范围变成了 [ 44 , 46 ] [44,46] [44,46]
  8. 范围变成了 [ 45 , 45 ] [45,45] [45,45]

实际上,这是 [ 1 , 50 ] [1,50] [1,50] 内最长的步骤了,去除第一步和最后一步,一共是 6 6 6 步,在 [ 1 , 50 ] [1,50] [1,50] 内,查找任意一个整数最多搜索 6 6 6

为什么呢?

每一次搜索范围都缩小一半,能搜几次不就是等于能除以多少个 2 2 2 吗?由于 2 6 = 64 > 50 2^6 = 64 > 50 26=64>50 ,所以 6 6 6 次之内必定能搜完

二分模板

二分的板子有很多种形式,别记那些左开右闭,左闭右开,我推荐你使用我这种超级好用的模板

//求范围[i,j]内的数
int l = i - 1, r = j + 1; //都要+1以免遗漏
while(l + 1 < r) //关键一步,不要写错
{
    int mid = (l + r) / 2;
    if(check(mid)) l = mid;//如果mid小了,那么将增加左边界
    else r = mid;
}

关于模板为什么这样写我就不多说了,来,开始干题

刷题

二分查找题目的精髓在于 check函数,可以说,没有 check函数绝对写不出二分,没有不写 check的二分

来吧!上题目

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-v2RxaNrC-1690019443693)(C:\Users\david\AppData\Roaming\Typora\typora-user-images\image-20230713195212263.png)]

第一题

我们用 s [ i ] s[i] s[i] 表示 a [ 1 ] + a [ 2 ] + . . . + a [ i ] a[1] + a[2] + ...+ a[i] a[1]+a[2]+...+a[i] a [ i ] a[i] a[i] 的前缀和,当一个栋楼的最大房间号小于需要查找的房间号,就说明往后、往大的搜了,否则就往前,往小的搜

输出也很有意思,输出的是第 f f f 栋楼的第 k k k 个房间

f f f 栋楼和房间号都知道了,我们怎么输出第几个房间呢?我们只需要输出当前房间号减去上一栋楼的最大房间号就可以了

#include <iostream>
#include <cstdio>
#include <algorithm> 
#define int long long
using namespace std;
const int MAXN = 2e5 + 10;
int n, m;
int a[MAXN], b[MAXN], s[MAXN], mx;
int find(int x) //目标门牌号 
{
	int l = 0, r = n + 1; //表示几栋楼 
	while(l + 1 < r)
	{
		int mid = (l + r) >> 1;
		if(s[mid] < x) l = mid; //如果s[mid]最大门牌号小于x,增加左边界 
		else r = mid;
	}
	//求最大比它小的 
	cout << r << ' ' << x - s[r-1] << endl;
}
signed main()
{
	cin >> n >> m;
	for(int i=1;i<=n;i++) cin >> a[i], s[i] = s[i-1] + a[i];
	while(m--)
	{
		int x;
		cin >> x;
		find(x);
	}
	return 0;
}

第二题

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-e8XTQg3J-1690019443695)(C:\Users\david\AppData\Roaming\Typora\typora-user-images\image-20230713200755499.png)]

这题不用前缀和,就算是二分,复杂度也比较大,是过不了这题的时空限制的

这题运用前缀和求出 ∑ j = l i r i [ w j ≥ W ] , ∑ j = l i r i [ w j ≥ W ] v i \sum_{j=l_i}^{r_i} [w_j \geq W],\sum _{j=l_i}^{r_i}[w_j \geq W]v_i j=liri[wjW],j=liri[wjW]vi

然后再把 y i y_i yi 算出来,算出来,最后记录答案是检验结果与标准值的差而不是检验结果

a#include<iostream>
#include<cstdio>
#include<cmath>
#define int long long
using namespace std;
const int MAXN = 200010;
int n, m, s, l = 0x7fffffffffffff, r;
int w[MAXN], v[MAXN], a[MAXN], b[MAXN], ans = 0x7ffffffffffffff;
int s1[MAXN], s2[MAXN], sum;
bool check(int x)
{
	for(int i=1;i<=n;i++) 
	{
		s1[i] = s1[i-1], s2[i] = s2[i-1];
		if(w[i] >= x) s1[i] += v[i], ++s2[i];
	}
	int y = 0;
	for(int i=1;i<=m;i++) y += (s1[b[i]]-s1[a[i]-1]) * (s2[b[i]]-s2[a[i]-1]);
	sum = abs(y-s);
	return y > s;
}
signed main()
{
	cin >> n >> m >> s;
	for(int i=1;i<=n;i++) 
	{
		scanf("%d %d", &w[i], &v[i]);
		l = min(l, w[i]);
		r += w[i];

	}
	--l, ++r;
	for(int i=1;i<=m;i++) cin >> a[i] >> b[i];
	while(l + 1 < r)
	{
		int mid = (l + r) >> 1;
		if(check(mid)) l = mid;
		else r = mid;
		ans = min(sum, ans);
	}	
	cout << ans;
	return 0; 
}
while(l + 1 < r)
{
	int mid = (l + r) >> 1;
	if(check(mid)) l = mid;
	else r = mid;
	ans = min(sum, ans);
}	
cout << ans;
return 0; 

}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值