二分答案模板

本篇主要介绍二分答案的几个模板

1.常用二分模板

整数二分模板1

  1. 将区间划分为[l,mid]和[mid+1,r]

  2. 则对应的边界更新操作为r=mid,和l=mid+1;

  3. 中点mid不要+1(相当于向下取整);

    //整数二分模板1
    int bsearch_1(int l,int r)
    {
        while(l < r)
        {
            mid = (l+r)>>1;
            if(check(mid))  r = mid;
            else    l = mid+1;
        }
        return l;
    }

整数二分模板2

  1. 将区间划分为[l,mid-1]和[mid,r]

  2. 则对应的边界更新操作为r=mid-1,和l=mid;

  3. 中点mid要+1(相当于向上取整);

    //整数二分模板2
    int bsearch_2(int l,int r)
    {
        while(l < r)
        {
            mid = (l+r+1)>>1;
            if(check(mid))  r = mid-1;
            else    l = mid;
        }
        return l;
    }

实数二分模板1

  1. 因为对实数而言,任意两个数中间都有无数个点存在,因此实数的二分都会有精度要求

    所以对于题目所给精度k,一般二分时的精度esp = 1e-(k+2);

    例如保留4位小数,则esp设置为1e-6;

  2. 因此实数二分查找的一般都是某个区间,对于一些点需要进行特殊的判定

  3. 然后关于左右区间的判定都与所写的check函数的条件有关

    double bsearch_d1(double l,double r)
    {
        double esp = 1e-6;//1e -(k+2),k为保留k位小数
        mid = (l+r)*0.5;//或mid = (l+r)/2;此时注意不能使用位运算
        while(r-l > esp)
        {
            if(check(mid))  r = mid;//去掉右区间
            else    l = mid;//去掉左区间
        }
        return l;
    }

实数二分模板2

  1. 基于实数的二分迭代无法进行彻底,只能将所求值在某个精度要求下近似

  2. 因此可以用近似迭代降低一部分精度,节省出一部分运行时间

  3. 根据某up主关于yxc的二分总结,迭代100次左右,结果基本稳定;代码如下:

    double bsearch_d1(double l,double r)
    {
        double esp = 1e-6;//1e -(k+2),k为保留k位小数
        for(int i = 1;i <= 100;i++)
        {
            double mid = (l+r)*0.5;
            if(check(mid))  r = mid;//去掉右区间
            else    l = mid;//去掉左区间
        }
        return l;
    }

eg1 洛谷P1024 [NOIP2001 提高组] 一元三次方程求解

[NOIP2001 提高组] 一元三次方程求解

题目描述

有形如:$a x^3 + b x^2 + c x + d = 0$ 这样的一个一元三次方程。给出该方程中各项的系数($a,b,c,d$ 均为实数),并约定该方程存在三个不同实根(根的范围在 $-100$ 至 $100$ 之间),且根与根之差的绝对值 $\ge 1$。要求由小到大依次在同一行输出这三个实根(根与根之间留有空格),并精确到小数点后 $2$ 位。

提示:记方程 $f(x) = 0$,若存在 $2$ 个数 $x_1$ 和 $x_2$,且 $x_1 < x_2$,$f(x_1) \times f(x_2) < 0$,则在 $(x_1, x_2)$ 之间一定有一个根。

输入格式

一行,$4$ 个实数 $a, b, c, d$。

输出格式

一行,$3$ 个实根,从小到大输出,并精确到小数点后 $2$ 位。

样例 #1

样例输入 #1

1 -5 -4 20

样例输出 #1

-2.00 2.00 5.00

提示

【题目来源】

NOIP 2001 提高组第一题

ac代码:
    #include<bits/stdc++.h>
    #define N 1005
    #define mod 998244353
    #define float double
    using namespace std;
    typedef long long ll;
    double a,b,c,d;
    float check(float m)
    {
        float num = a*m*m*m+b*m*m+c*m+d;
        return num ;
    }
    void solve()
    {
        cin>>a>>b>>c>>d;
        float x,ans;
        for(float i = -100;i <= 100;i++)//因为题目规定了答案区间,所以只需要在[-100,100]之间进行遍历
        {
            float l = i;
            float r = l+1;
            if(check(i)==0) cout<<fixed<<setprecision(2)<<i<<" ";//这里进行特殊判断就是上文中提到的对于实数二分只能确定某个区间然后从两侧近似,若区间 值恰好相等则进行特判
            else
            {
                if(check(l) * check(r) < 0)
                {
                    while((r-l) > 1e-4)//注意精度 esp = 1e -(k+2);
                    {
                        float mid = (l+r)/2;
                        if(check(l) * check(mid) <= 0)   r = mid;
                        else    l = mid;
                    }
                    cout<<fixed<<setprecision(2)<<l<<" ";//不使用printf的控制小数位数的方法
                }
            }
        }
    }
    int main()
    {
        ios::sync_with_stdio(false);
        cin.tie(nullptr);
        cout.tie(nullptr);
        solve();
        return 0;
    }

关于二分答案的细节处理

在11.11下午,面对一道极其明显且板子二分答案,graspppp陷入了沉思,在样例过了之后wa了数十发才找到了正解,这也确实帮他在二分答案的理解上有了更深的认识

1.边界条件的确定,以洛谷P1873 [COCI 2011/2012 #5] EKO / 砍树为例,在这道题的边界处理上,更得注意细节,否则就是10pts与ac的天差地别。

那么针对这道题来说,设s为最大的整数高度,则根据单调性而言,小于s的所有答案都会让总的木材数量过多,大于x的所有答案都会导致收集到的木材数量不足。那么对于check函数而言:
     bool check(int x)
    {
        int sum = 0;
        for(int i = 1;i <= n;i++)
        {
            if(a[i] > x)    sum+=a[i]-x;
            if(sum >= H) break;
        }
        return sum >= H;
    }   
需要注意,此时sum >= H 与sum > H的区别

首先需要搞清楚check函数在此处的定义

1.check 返回1时,说明当最大的整数高度为s时,此时收集到的木材数量恰好或大于所需

2.因此会影响到下面两个模板的处理方法

模板1 将区间划分为[l,mid]和[mid+1,r];

    while(l < r)
    {
        int mid = (l+r)>>1;
        if(check(mid))  l = mid  + 1;//结合check函数概念
        else    r = mid;//此时若check为真,则s<l时一定为真
    }
    cout<<r-1;//假做法!!!!!!!!

分类讨论:

若sum >= H,则**代表当前的l可能为答案**,即l为所需要的答案时候,那么

就代表当前r为右边界,l为左边界,此时的操作为l再往右边寻找,如果找到更

大的l,重复操作,此时l是肯定符合条件的,但是如果l+1向右寻找之后不符合条件的话怎么办呢?

这就与上面check函数的概念有关了,所以这里我的r-1是假做法,正确处理方式应该是修改上面check函数或者采用其他模板

为此,引入模板2

模板2

    while(l < r)
    {
        int mid = (l+r+1)>>1;
        if(check(mid))  l = mid ;//若check为真,说明s<l时一定为真,应当向右搜索
        else    r = mid -1;
    }
    cout<<l;
若sum >= H,则代表当前的l可能为答案,同时,也代表着如果l不成立,则l右边的(l,r)也不会成立,即l为所需要的答案时候,那么

就代表当前r为右边界,l为左边界,此时的操作为r再往左边寻找,如果找到更小的r,重复操作,此时l是肯定符合条件的,所以最后的可行解一定为l

总结:在处理边界问题时,需要根据实际情况与check函数定义选择模板(不要和我一样再盲目的瞎改l和r的+-1来得到想要的答案了,否则也是徒劳)

学成归来的冰,分享一种刚学的好用的不用考虑那么多的二分模板!!!

from五点七边:为什么二分查找总是写错;

    while(l + 1 != r)
    {
        mid = (l+r)>>1;
        if(check)   l = mid (r = mid);//根据实际情况判断
        else    r = mid(l = mid);//同上
        cout<<l或r;//同样根据实际情况判断
    }

据此,我们就可以简化问题,得到如下二分ac代码,真是十分简洁+美观啊
    while(l + 1 != r)
    {
        int mid = (l+r)>>1;
        if(check(mid))  l = mid  ;//结合check函数概念,若当前的sum>所需的木材,则往右找
        else    r = mid ;//
    }
    cout<<l;

是否会陷入死循环呢:答案->不会具体可以点击上面的视频链接自行查找;;

完,感谢阅读~

在此推荐一位更详细的介绍二分答案边界处理的博文

posted @   graspppp  阅读(65)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示