二分算法模板


# 整数二分

模板

bool check(int x) {
    
    /* ... */} // 检查x是否满足某种性质

// 区间[l, r]被划分成[l, mid]和[mid + 1, r]时使用:
int bsearch_1(int l, int r)//mid找左边界
{
    
    
    while (l < r)
    {
    
    
        int mid = l + r >> 1;
        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)//mid找的是右边界
{
    
    
    while (l < r)
    {
    
    
        int mid = l + r + 1 >> 1;
        if (check(mid)) l = mid;
        else r = mid - 1;
    }
    return l;
}

作者:yxc
链接:https://www.acwing.com/blog/content/277/
来源:AcWing

总结一下,每一次的判断都会产生l=mid或者r=mid,对于l+r是否要补充1,看l=mid否
如果是l=mid 补1,也就是mid=l+r+1>>1;
关于为什么补1,看原视频讲解

求数的范围(一个数的左右边界)

例题来源

#include<iostream>
#include<algorithm>
using namespace std;
const int N = 100005;
int main()
{
    
    
	int arr[N];
	int n, q;
	cin >> n >> q;
	for (int i = 0; i < n; i++)
		cin >> arr[i];
	int beg, end;
	for (int i = 0; i < q; i++)
	{
    
    
		int x;
		cin >> x;
		int l = 0, r = n - 1,mid;
		while (l < r)//右边界
		{
    
    
			mid = l + r + 1 >> 1;
			if (arr[mid] <= x) l = mid;
			else r = mid - 1;
		}
		if (arr[l] != x) cout << "-1 -1" << endl;
		else
		{
    
    
			end = l;
			l = 0, r = n - 1;
			while (l < r)//左边界
			{
    
    
				mid = l + r >> 1;
				if (arr[mid] >= x) r = mid;
				else l = mid + 1;
			}
			beg = r;
			cout << beg << " " << end << endl;
		}
		
	}
	return 0;
}

对于为什么是找右边界这一段二分进行讲解:

int mid = l + r + 1 >> 1;
while (l < r)//找右边界
		{
    
    
			if (arr[mid] <= x) l = mid;
			else r = mid - 1;
			mid = r + l + 1 >> 1;
		}

例如数据1 2 3 3 4 4 5 找3
如果arr[mid]<=x 那么要找的数就是再mid的右边 所以l=mid
当arr[mid]>x 要找的数就在mid的左边,并且mid所指的这个数不成立,因为找的是右边界,所以r=mid-1(在mid的左边,可能就是mid往前一位)

而如果找的是左边界

int mid = l + r >> 1;
			while (l < r)//找左边界
			{
    
    
				if (arr[mid] >= x) r = mid;
				else l = mid + 1;
				mid = l + r>> 1;
			}

arr[mid]>=x 那么要找的值在mid的左边 且是左边界,所以r=mid
arr[mid]<x 也就是要找的数在mid的右边,所以l=mid+1
(好像理了一遍就清楚了,第一次写的时候不知道为什么那么乱,果然还是菜的原因…)

在比赛的时候有两个非常好用的的函数代替二分算法
(但二分还是要好好学的)

lower_bound(beg,end,num);

从数组的begin位置到end-1位置二分查找第一个大于或等于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。

upper_bound(beg,end,num);

从数组的begin位置到end-1位置二分查找第一个大于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。

扫描二维码关注公众号,回复: 13285289 查看本文章

浮点数二分

模板

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;
        //浮点数的二分能精确取到l和r的中间值,所以不用补1
        //整数二分是因为二分是像下取整,如果l=r-1,那么r+l>>1==l,还是区间(l,r)死循环了
        if (check(mid)) r = mid;
        else l = mid;
    }
    return l;
}
作者:yxc
链接:https://www.acwing.com/blog/content/277/
来源:AcWing

求数的平方根

#include<iostream>
#include<algorithm>
using namespace std;
int main()
{
    
    
	double x;
	cin >> x;
	double beg = 0, end = x, mid;
	while (end - beg > 1e-8)//只要误差过大就一直缩小误差
	{
    
    
		mid = (beg + end) / 2;
		if (mid * mid >= x) end = mid;
		else beg = mid;
	}
	printf("%f",beg);
	return 0;
}

这个对于x<1解决不了

  • 解决方法1:根据题目给出的数的范围直接把l,r开到最大,例如题目说x范围在0–10000,那么 double beg = 0, end = 10000;
  • 解决方法2:处理不了x<1的情况因为x的开方比x大,所以end=max(1,x)这样就可以解决

对于精确值那边的处理,如果题目要求保留四位小数就是1e-6,六位小数就是1e-8,反正精度往上加两位。

求立方根其实差不多,就是把if判断改成midmidmid,注意负数可以开立方

#include<iostream>
#include<algorithm>
using namespace std;
int main()
{
    
    
	double x;
	cin >> x;
	double beg, end, mid;
    beg=-10000,end=10000;
	while (end - beg > 1e-8)
	{
    
    
		mid = (beg + end) / 2;
		if (mid * mid * mid >= x) end = mid;
		else beg = mid;
	}
	printf("%f",beg);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_50816938/article/details/118829032