整数二分与实数二分

文章讲述了整数二分搜索算法,包括其原理、如何确定首次和最后一次出现位置的代码模板,以及易错点和证明方法。同时介绍了实数二分的原理和处理精度问题的技巧。
摘要由CSDN通过智能技术生成

整数二分

  • 背景: 给定一个单调不减的序列, 每次指定一个数 x ,问 x 在序列中第一次与最后一次出现的位置
  • 原理: 根据序列的单调性, 可以把序列分为两个区间, 每次判断中点是否满足要求即可舍去另一半区间
  • 时间复杂度: O ( l o g   n ) O(log\ n) O(log n)
  • 易错点: 区间边界错误导致程序死循环
  • Tips: 理解并记忆模板,熟能生巧

下面给出一份二分模板并证明为什么不会出现死循环

整数二分模板

// 数据存在数组a中, 下标从1到n, x为待查询的数

// 第一次出现的位置
int l = 1, r = n;
while(l < r) // 循环终止与 l == r
{
	int mid = (l + r) / 2;
	if(a[mid] >= x) r = mid;
	else l = mid + 1;
}
cout << l;

// 最后一次出现的位置
int l = 1, r = n;
while(l < r) // 循环终止与 l == r
{
	int mid = (l + r + 1) / 2;
	if(a[mid] <= x) l = mid;
	else l = mid - 1;
}
cout << l;

证明

以第一份代码为例, 下面给出一个样例 l = 1 , r = 2 l = 1, r = 2 l=1,r=2
易知 m i d = ( l + r ) / 2 = ( 1 + 2 ) / 2 = 1 mid = (l + r) / 2 = (1 + 2) / 2 = 1 mid=(l+r)/2=(1+2)/2=1
l = m i d + 1 l = mid + 1 l=mid+1, 此时 l = 2 , r = 2 l = 2, r = 2 l=2,r=2, 正常退出循环
修改模板为 l = m i d l = mid l=mid, 此时 l = 1 , r = 2 l = 1, r = 2 l=1,r=2, 代码一直循环求mid无法退出

根据样例可知, 每次求 m i d mid mid 修改区间后, 新区间必然要比原区间更小, 否则就会出现死循环

欲证明原命题, 即证当 l < r l < r l<r 时: m i d < r mid < r mid<r m i d + 1 > l mid + 1 > l mid+1>l
根据代码可知 m i d = ⌊ l + r 2 ⌋ ≤ ⌊ r − 1 + r 2 ⌋ = r − 1 < r mid = \lfloor \frac{l + r}{2} \rfloor \leq \lfloor \frac{r - 1 + r}{2} \rfloor = r - 1 < r mid=2l+r2r1+r=r1<r
m i d + 1 = ⌊ l + r + 2 2 ⌋ > = ⌊ l + l + 1 + 2 2 ⌋ = l + 1 > l mid + 1 = \lfloor \frac{l + r + 2}{2} \rfloor >= \lfloor \frac{l + l + 1 + 2}{2} \rfloor = l + 1 > l mid+1=2l+r+2>=2l+l+1+2=l+1>l
证毕

例题

Acwing 791: 数的范围

AC code

#include <iostream>
using namespace std;
const int N = 1E5 + 10;

int n, m;
int a[N];

int main()
{
    cin >> n >> m;
    for(int i = 1; i <= n; i ++ ) cin >> a[i];
    
    while(m -- )
    {
        int l = 1, r = n, x;
        cin >> x;
        
        while(l < r)
        {
            int mid = (l + r) / 2;
            if(a[mid] >= x) r = mid;
            else l = mid + 1;
        }
        
        if(a[l] != x) cout << "-1 -1\n";
        else
        {
            cout << l - 1 << ' ';
            int l = 1, r = n;
            while(l < r)
            {
                int mid = (l + r + 1) / 2;
                if(a[mid] <= x) l = mid;
                else r = mid - 1;
            }
            cout << l - 1 <<'\n';
        }
    }
    return 0;
}

实数二分

  • 背景: 求数的立方根
  • 原理: 根据函数的单调性, 可以将当前的区间分为两个区间, 每次判断中点来舍去一半区间
  • 时间复杂度: O ( l o g   n ) O(log\ n) O(log n)
  • 易错点: eps过大导致精度不够或者eps过小导致运行时间长
  • Tips: eps一般取题目要求精度的 1 % 1\% 1%

模板

double l = 0, r = 1E9, eps = 1E-5;
while(r - l > eps)
{
	double mid = (l + r) / 2;
	if(mid * mid * mid >= x) r = mid;
	else l = mid;
}

实数二分与整数二分不同, 不会因为区间边界问题导致死循环

样例

Acwing 790: 数的三次方根

AC code

#include<cstdio>
using namespace std;

double n;

int main()
{
    scanf("%lf", &n);
    double l = -1E4, r = 1E4, eps = 1E-8;
    while(r - l >= eps)
    {
        double mid = (l + r) / 2;
        if(mid * mid * mid >= n) r = mid;
        else l = mid;
    }
    printf("%.6lf", l);
    return 0;
}
  • 19
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值