二分

枚举是蛮力法的应用,所谓蛮力,并非指用人脑的智力或体力,而是利用计算机的强大特性进行暴力计算并验证的方式。
最朴素的枚举法是线性枚举,线性枚举优化方案有二分枚举、三分枚举等。

二分

二分是分治法的一种思想,可分为整数二分、实数二分等。二分的应用条件线性函数,必须为严格的单调有序序列,若无序则必须先排序再二分。常见应用为二分查找和二分答案。

注:对于区间 [ l , r ] [l,r] [l,r] [ l , r ) [l,r) [l,r),其二分写法的边界处理不同,需要特别注意。

整数二分

整数二分的中值计算
实现原理适用情形潜在问题
m = ( l + r ) / 2 m=(l+r)/2 m=(l+r)/2求区间边界平均数,即为区间中值 l ≥ 0 , r ≥ 0 , l + r l\ge0,r\ge0,l+r l0,r0,l+r无溢出 l + r l+r l+r可能溢出,负数情形下向0取整
m = l + ( r − l ) / 2 m=l+(r-l)/2 m=l+(rl)/2 1 2 \frac{1}{2} 21区间长度加在区间左边界上,即为区间中值 l − r l-r lr无溢出 r , l r,l r,l一正一负, r − l r-l rl可能溢出
m = l + r > > 1 m=l+r>>1 m=l+r>>1 m = ( l + r ) / 2 m=(l+r)/2 m=(l+r)/2原理相同,通过位运算实现 l + r l+r l+r无溢出 l + r l+r l+r可能溢出
二分边界问题

确定边界的更新核心是确定二分区间为 [ l , r ] [l,r] [l,r] [ l , r ) [l,r) [l,r)情形,需确保左右边界始终为当前情形不变。

  • [ l , r ] [l,r] [l,r]情形:1. 允许 l = r l=r l=r,即 w h i l e ( l < = r ) while(l<=r) while(l<=r) 2. 因左右边界必须都是合法值,因此更新边界时候均需排除不合法的 m m m,即为 l = m + 1 l=m+1 l=m+1 r = m − 1 r=m-1 r=m1
extern int l=0,r=MAX-1,m;
extern bool check(int x);
bool bs(){
    while(l<=r){
        m=l+r>>1;
        if(check(m)) //l=m+1或r=m-1
        else //r=m-1或l=m+1
    }
}
  • [ l , r ) [l,r) [l,r)情形:1. 不允许 l = r l=r l=r,即 w h i l e ( l < r ) while(l<r) while(l<r) 2.因左边界合法,右边界不合法,因此左边界更新需排除不合法的 m m m右边界更新无需排除不合法的 m m m
extern int l=0,r=MAX,m;
extern bool check(int x);
bool bs(){
    while(l<r){
        m=l+r>>1;
        if(check(m)) //l=m+1或r=m
        else //r=m或l=m+1
    }
}

实数二分

实数二分与整数二分的区别:

  1. 浮点数不支持位运算,故实数二分中值mid的计算应采用 l + ( r − l ) / 2 l+(r-l)/2 l+(rl)/2
  2. 应控制精度:在计算机科学中,由于浮点数存储机制问题,2个浮点数只能互相无限趋近不存在2个严格相等的浮点数,因此需考虑精度问题。常用数学中表示趋向无穷小的正数 ϵ \epsilon ϵ作为可接受误差范围以控制精度, ϵ \epsilon ϵ设计需合理,过小的 ϵ \epsilon ϵ精度高但会超时,过大的 ϵ \epsilon ϵ会导致精度低不符合要求。
  3. 边界更新不同:实数二分的边界一般为左开右开问题,因此更边界为中值即可,无需 ± \pm ± ϵ \epsilon ϵ
extern double l,r,m;
extern bool check(double x);
const double eps=1e-7;//控制精度
bool bs(){
    while(r-l>eps){
        mid=l+(r-l)/2;
        if(check(m)) //... r=m;
        else //... l=m;
    }
}

二分查找

[ l , r ] [l,r] [l,r]区间内查找某个数据是否存在,最直接的方法是采用枚举法,线性枚举的复杂度为 O ( n ) O(n) O(n),超时。最常见的优化是进行二分枚举,复杂度为 O ( log ⁡ 2 ( n ) ) O(\log_2(n)) O(log2(n)),通过二分枚举进行查找数据的过程称为二分查找。二分查找可看做在所给区间范围内进行二分答案。下面以整数二分查找为例进行说明。

二分查找单个元素: O ( log ⁡ 2 ( n ) O(\log_2(n) O(log2(n)

extern int a[MAX],key,l=0,r=MAX-1,m;
bool bs(){
    while(l<=r){
        m=l+r>>1;
        if(a[m]==key) return 1;
        else if(a[m]>key) r=m-1;//注意整数二分由于取整问题,mid需要+1或-1
        else if(a[m]<key) l=m+1;
    }
    return 0;
}

二分查找边界

  1. 先二分查找,再线性查找:整体复杂度仍为 O ( n ) O(n) O(n),时间性能退化为线性阶
extern int a[MAX],key,l=0,r=MAX-1,m;
bool bs(){
    while(l<=r){
        m=l+r>>1;
        if(a[m]==key){
            int nl=m,nr=m;//左右边界
            while((nl-1)!=0&&a[nl-1]==key) nl--;//线性查找左边界
            while((nr+1)!=MAX-1&&a[nr+1]==key) nr++;//线性查找右边界
            return 1;
        }else if(a[m]<key) l=m+1;
        else if(a[m]>key) r=m-1;
    }
    return 0;
}
  1. 持续二分查找
  • 查找单侧边界情形
extern int a[MAX],key,l,r,m,ans;
bool bs(){
    while(l<=r){
        m=l+r>>1;
        if(a[m]<key) l=m+1;
        else if(a[m]>key) r=m-1;
        else r=m-1;//查找左边界 l=m+1则为查找右边界
    }
}
  • 查找双边界情形:巧妙之处在于构造一个 ≥ \ge 当前元素的值,并进行二分查找,无需关心该值是否存在,若不存在则会指向首个大于key的元素。右边界则为构造值-1。

C二分查找函数——bsearch(<stdlib.h>)

参数:key地址、数组名、元素个数、元素大小、自定义比较规则

返回值:若查找成功返回指向该元素的指针,否则返回NULL

C++二分查找函数(<algorithm>)

  • binary_search:查找首个** = = =**给定值的元素 返回值:找到返回1,否则返回0

  • lower_bound:查找首个 ≥ \geq 给定值的元素

  • upper_bound:查找首个 > > >给定值的元素

    注:upper_bound是找严格>,因此在数组中位置相对较高,为upper;lower_bound找的是**>=,在数组中位置相对较低,为lower。
    返回值:
    1.指针法使用:若找到则返回指向满足指定条件元素的
    指针**,否则返回NULL
    2.迭代器法使用:若找到则返回指向满足指定条件元素的迭代器,否则返回end()

二分答案

枚举法:若可通过某种映射关系,推断出答案具有严格单调性,并且可确定所在的合法区间的上下界,则可通过对边界进行不断更新,从而缩小合法区间,不断试探答案。枚举法的核心在于合法边界的确定以及check函数的设计。

线性枚举:暴力枚举。不断更新合法区间下界,当合法区间下界不符合要求时,则上一次枚举出的下界即为正确答案。复杂度为 O ( n ) O(n) O(n)​,易超时。

extern int l,r;//l:答案下界 r:答案上界
bool check(int x){
    int count;//记录满足条件的实例数
    for(){//验证给出所有实例在x的条件下是否都能符合要求(广度)
        if() count++;
    }
    if() return 1;//若当前x在所有实例都满足条件情形下,此x为一个可行解
    else return 0;//否则此答案不可行,若为线性枚举答案即为上一次枚举出的x,二分枚举则继续缩小答案区间
}
int verify(){
    for(int i=l;i<=r;i++){
        if(check(i)) ans=i;//该答案允许,但可能并非满足要求的最优解,继续向答案上界方向进行线性枚举
        else break;//答案不符,说明上一次找到的答案已为最优解
   	return ans;
}

二分枚举:不断更新合法区间上下界,复杂度为 O ( log ⁡ 2 ( n ) ) O(\log_2(n)) O(log2(n))。通过二分枚举合法区间、试探答案的过程,称为二分答案。二分答案最大优点是将复杂度由线性阶( O ( n ) O(n) O(n))优化至对数阶( O ( l o g 2 ( n ) ) O(log_2(n)) O(log2(n)))。下面以整数二分为例说明二分答案。

extern int l,r,ans,mid;//l:答案下界 r:答案上界
extern bool check(int x);//检查答案正确性,check函数同线性枚举
int verify(){
    while(l<r){
        m=l+r>>1;
        if(check(m)) ans=m; //... 答案允许,但可能并不是满足要求的最优解。移动r或l,更新答案上下界确定最优解
        else //... 答案不符,移动l或r
	}
    return ans;
}

二分答案应用实例:最值中的最值问题(最大值最小化/最小值最大化问题)

三分

三分法是二分法的扩展,三分适用于求解单峰/单谷函数的最值问题。三分法要求极值点左右两侧具有严格的单调性,即有且仅存在一个极值点。

三分法用两个三等分点 m i d 1 mid1 mid1 m i d 2 mid2 mid2将答案区间分为三份,复杂度为 O ( log ⁡ 3 ( n ) ) O(\log_3(n)) O(log3(n))​。计算方法:

double k=(r-l)/3.0;
double mid1=l+k,mid2=r-k;

以单峰函数为例,讨论三分法的情形:

  1. f ( m i d 1 ) < f ( m i d 2 ) f(mid1)<f(mid2) f(mid1)<f(mid2),则 m i d 1 mid1 mid1 m i d 2 mid2 mid2均位于答案左侧,或 m i d 1 mid1 mid1位于答案左侧, m i d 2 mid2 mid2位于答案右侧。之后令 l = m i d 1 l=mid1 l=mid1
  2. f ( m i d 1 ) > f ( m i d 2 ) f(mid1)>f(mid2) f(mid1)>f(mid2),则 m i d 1 mid1 mid1 m i d 2 mid2 mid2均位于答案右侧,或 m i d 1 mid1 mid1位于答案左侧, m i d 2 mid2 mid2位于答案右侧。之后令 r = m i d 2 r=mid2 r=mid2

实数三分

extern double l,r,mid1,mid2;
extern bool check(double x);
const double eps=1e-7;//控制精度
bool ts(){
    while(r-l>eps){
        double t=(l+r)/3.0;
        mid1=l+t,mid2=r-t;
        if(check(mid1)>check(mid2)) //... r=mid2;
        else //... l=mid1;
    }
}

整数三分

extern int l,r,mid1,mid2;
extern bool check(int x){
    while(r-l>2){//2或其他数 绝不能写成r>l
        mid1=l+(r-l)/3,mid2=r-(r-l)/3;
        if(check(mid1)>check(mid2)) //... 移动r
        else //... 移动l
    }
}
  • 26
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值