整数二分和浮点数二分基本思路和模板

————出自南昌理工学院ACM集训队

整数二分基本思想

二分,是从一系列有序对中找出某一个符合条件的数,找到之后把这个数的位置给返回,
基本思想就是,我给你一个数据范围,打个比方1—200,我让你猜我心中所想的那个数175,最基本的方法就是从头到尾开始猜,是1吗,不是,是2吗,不是。。。,一直猜到答案为止,这样猜的话,最坏的情况是要猜200次。
不过用二分来找就更为巧妙一点,你看,要从1—200内猜一个数,那我们可以直接先取1—200的一半,就直接从100开始猜,我会告诉你小了,小了就直接再猜100—200的一半150,我又会告诉你小了,你就再可以从150—200中,取中间值175,这样就猜对了。
现在让我们来归纳一下二分基本思想:二分就是每次取数据范围的一半,然后判断一半这个点是否符合条件,然后来选择是取这个点的上半部分还是下半部分,之后再取选择完后的范围再取中间点,以此循环,直到找到符合条件的答案为止。

模板:

bool check(int x) {/* ... */} // 检查x是否满足某种性质
// 区间[l, r]被划分成[l, mid]和[mid + 1, r]时使用:
//查找左边界时
int bsearch_1(int l, int r)
{
    while (l < r)//循环到最后l和r相差1就为答案
    {
        int mid = l + r >> 1;//范围的中间值   >>1比/2稍微要快一点
        if (check(mid)) r = mid;    // check()判断mid是否满足性质
        else l = mid + 1;
    }
    return l;
}
// 区间[l, r]被划分成[l, mid - 1]和[mid, r]时使用:
//查找右边界时
int bsearch_2(int l, int r)
{
    while (l < r)
    {
        int mid = l + r + 1 >> 1;//如果mid=l+r>>1的话会陷入死循环
        if (check(mid)) l = mid;
        else r = mid - 1;
    }
    return l;
}
//作者:yxc
//链接:https://www.acwing.com/blog/content/277/

例题

给定一个按照升序排列的长度为n的整数数组,以及 q 个查询。对于每个查询,返回一个元素k的起始位置和终止位置(位置从0开始计数)。如果数组中不存在该元素,则返回“-1 -1”。

输入格式
第一行包含整数n和q,表示数组长度和询问个数。
第二行包含n个整数(均在1~10000范围内),表示完整数组。
接下来q行,每行包含一个整数k,表示一个询问元素。

输出格式
共q行,每行包含两个整数,表示所求元素的起始位置和终止位置。
如果数组中不存在该元素,则返回“-1 -1”。
数据范围
1≤n≤100000
1≤q≤10000
1≤k≤10000
输入

6 3
1 2 2 3 3 4
3
4
5

输出

3 4
5 5
-1 -1

题意已经很清楚就是求一个数的初始位置和终止位置。
代码:

#include <iostream>
#include <stdio.h>
#include <algorithm>
#include <cstring> 
typedef long long ll;
using namespace std;
const int N = 1e5 + 5;
int main()
{
	int n, m,a[N];
    scanf("%d %d", &n, &m);
	for (int i = 0; i < n; i++) {
		 scanf("%d", &a[i]);
	}
    while (m--) {
		int x;
		scanf("%d", &x);
	     int l = 0, r = n - 1;//定义边界
	     while (l < r) {//找的是左边界也就是要查询数的初始位置
		      int mid = l + r >> 1;//取中间的点
		       if (a[mid] >= x) r = mid;//a[mid]大于x,就将区间缩小为[l,mid]
		       else l = mid + 1;//小了就将区间更新为[mid+1,r],因为是整数二分,所以是mid+1
	     }
	     if (a[l] != x) {//找不到就输出-1 -1
		     cout << "-1 -1" << endl;
	      }
	     else {
		     cout << l << " ";
			 int l = 0, r = n - 1;
		     while (l < r) {//找要查询数的终止位置
			       int mid = l + r + 1 >> 1;
			       //查找的是右边界,所以右边界的左边都是<=a[mid]的
			        if (a[mid] <= x) l = mid;//a[mid]小了就将区间更新为[mid,r]
			        else r = mid - 1;//大了就更新为[mid-,r]
		     }
		     cout << l << endl;
	    }
	}
	return 0; 
}

浮点数二分基本思想

浮点数二分和整数二分不同,浮点数不存在由于(整数)取整导致的边界问题,每次二分区间严格减半, 因此比整数二分简单的多,每次更新边界时直接让r = mid或l = mid即可。

模板

bool check(double x) {/* ... */} // 检查x是否满足某种性质
double bsearch_3(double l, double r){
    const double eps = 1e-6;   // eps 表示精度,取决于题目对精度的要求
    while (r - l > eps){
        double mid = (l + r) / 2;
        if (check(mid)) r = mid;
        else l = mid;
    }
    return l;
}
//作者:yxc
//链接:https://www.acwing.com/blog/content/277/

让我们来看个例题

例题

给定一个浮点数n,求它的三次方根。
输入格式
共一行,包含一个浮点数n。
输出格式
共一行,包含一个浮点数,表示问题的解。注意,结果保留6位小数。
数据范围
−10000≤n≤10000
输入

1000.00

输出

10.000000

代码:

#include <iostream>
#include <stdio.h>
#include <algorithm>
#include <cstring> 
typedef long long ll;
using namespace std;
const int N = 1e5 + 5;
int main()
{
	double n;
    cin >> n;
	if (n < 0) {//如果为负数时,先转换一下
		n = -n;
		cout << "-";
	}
	double l = 0, r = n;
	while (r - l > 1e-8) {//一般取题目要求位数再加两位,题目为10^-6,所以我们取10^-8
		double mid = (l + r) / 2;
		if (mid * mid * mid >= n) r = mid;//边界都是直接更新一半
		else l = mid;
	}
	printf("%lf", l);//保留六位小数
	return 0; 
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值