二分搜索专题

二分搜索专题


前言

RT,本文专门讲解所谓的二分搜索。


什么是二分搜索?

这个二分搜索其实就是二分查找(Binary Search),效率是log级的,相对普通的n级搜索要快很多。

他的核心思路就是每一次折半,然后看看要找的元素与中点的关系再决定跳到左区间还是右区间

因此,二分搜索的最大前提也是唯一前提就是:所搜索的序列必须得有单调性。


实现

二分的实现不难,首先你需要一个下界和一个上界,作为ls和rs的初始值。

其次,取中间值mid,然和调用检查函数check。这个函数会判断在mid的情况下符不符合要求,如果符合的话那就看左区间(往小的看),不符合就看右区间。

当然要注意,ls和rs的更新种类蛮多,这里是直接更新为mid,也有一些会更新为mid+1和mid-1,具体情况视题目而定。

那么一般的实现如下:

int search(int item){
    int ls=low,rs=high,mid;
    while(ls<=rs){
        mid=(ls+rs)>>1;
        if(check(mid)){
            ls=mid;
        }else{
            rs=mid;
        }
    }
}

关于二分搜索模板的练习做一道就OK了: POJ-3273

这道题的题意大致是有n个数,然后希望把他们分成m组,对于每一组的和,问最大值的最小值为多少?

思路很暴力,首先答案肯定是在有限区间内,那么二分这个区间,假如对于当前的mid可以分组,那么跳左区间,反之跳右区间。

AC代码如下:

#include
#include
#include
using namespace std;

const int maxn=100100;

int n,m;
int a[maxn];

bool check(int mx){
    int t=0,cur=a[1];
    for(int i=1;i<=n;i++){
        if(cur>mx)return false;
        if(cur+a[i+1]>mx){
            cur=a[i+1];
            t++;
        }else{
            cur+=a[i+1];
        }
    }
    t++;
    if(t>m)return false;
    return true;
}

int search(){
    int l=1,r=1000000000,mid;
    while(l<=r){
        mid=(l+r)>>1;
        if(check(mid))r=mid-1;
        else l=mid+1;
    }
    return l;
}

int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)scanf("%d",&a[i]);
    printf("%d",search());
}

题目

二分搜索要掌握一点都不难,但是在题目中熟练运用还是需要一定技巧。

这里列举几道二分搜索的题,一道一道地过。


POJ-3258

这一道题就是跳石头的问题,当然可以从中拿走若干块,然后问你要跳的最小距离的最大值是多少?

其实与上一道题运用的想法都一样,但是要注意:check函数的写法截然不同。

AC的代码如下,可以感受一下两道题的差异:

#include
#include
#include
using namespace std;

const int maxn=50050;

int l,n,m;
int a[maxn];

bool check(int x){
    int start=0,t=0;
    for(int i=1;i<=n;i++){
        if(a[i]-startelse start=a[i];
    }
//  cout<
    if(l-startm)return false;
    return true;
}

int search(){
    int lt=0,r=l,mid;
    while(lt<=r){
        mid=(lt+r)>>1;
        if(check(mid)){
            lt=mid+1;
        }else{
            r=mid-1;
        }
    }
    return lt;
}

int main(){
    scanf("%d%d%d",&l,&n,&m);
    for(int i=1;i<=n;i++)scanf("%d",&a[i]);
    sort(a,a+n+1);
    printf("%d",search()-1);
}

POJ-3122

这道题是我做过的最毒瘤的几道题之一,题意是你有很多个不同半径的圆,要分给n个人以及你自己,每个人分一样的大小,然后问你最大可以分多大。

当然,你不可以把很多个残片拼在一起。

这题有几个坑:

第一个是在于π的值必须足够精确,我就直接调用了计算器的值。

第二个是在于有误差这种鬼东西,记得一定要开够。

最后一个是在于虽然朋友只有n个,却要分给n+1个人(包括你自己),我前x次都是这么错得。

除了这几个毒瘤点,这道题还算不错,AC代码如下:

#include
#include
#include
using namespace std;

const int maxn=50050;

int l,n,m;
int a[maxn];

bool check(int x){
    int start=0,t=0;
    for(int i=1;i<=n;i++){
        if(a[i]-startelse start=a[i];
    }
//  cout<
    if(l-startm)return false;
    return true;
}

int search(){
    int lt=0,r=l,mid;
    while(lt<=r){
        mid=(lt+r)>>1;
        if(check(mid)){
            lt=mid+1;
        }else{
            r=mid-1;
        }
    }
    return lt;
}

int main(){
    scanf("%d%d%d",&l,&n,&m);
    for(int i=1;i<=n;i++)scanf("%d",&a[i]);
    sort(a,a+n+1);
    printf("%d",search()-1);
}
//787

当前时间:2018.6.12 21:54

持续更新中。。。

你可能感兴趣的:(专题)