二分答案从入门到出门

目录

一、基础题目:

(1)基础格式(后继):

①为什么选择left=mid+1?

②mid的选择:

③mid的前驱或者后继

二、前驱例题:

三、后继例题:

(2)STL的upper_bound()和lowber_bound()

①upper_bound()

②lowber_bound()

使用方式:


二分作用:通过二分压缩数据范围,极大降低时间复杂度

一、基础题目:

P2249 【深基13.例1】查找 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

经典的二分查找题目,二分查找不会的可以另外学一下先 

 #include<iostream>
 using namespace std;
 const int N=1e6+10;
 int all[N];
 int main(){
     int cs,css;
     cin>>cs>>css;
     for(int x=1;x<=cs;x++){
         cin>>all[x];
     } 
     for(int x=1;x<=css;x++){
         int xq;
         cin>>xq;
         int l=1,r=cs+1;
         while(l<r){
             int mid=l+(r-l)/2;
             if(all[mid]>=xq){
                 r=mid;
             }
             else{
                 l=mid+1;
             }
         }
         if(x!=1)cout<<" ";
         if(all[l]==xq)cout<<l;
         else cout<<-1;
     }
 }

这道题没有前驱后继的要求,所以选择了用后继的思路。


(1)基础格式(后继):

 int all[n]
 int x;//x是寻找的数值
 int left=0,right=n;//[0,n)区间
 while(left<right){
     int mid=left+(left-right)/2;//避免溢出问题 ② ③
     if(a[mid]>=x) right=mid;
     else left=mid+1;//①
 }
 return left;

①为什么选择left=mid+1?

        mid计算时候会出现取整情况,如果left=mid的话会出现死循环

        例如:left=2,right=3,mid=left+(left-right)/2=2则后续的left=2,则进入死循环

②mid的选择:

1.mid=(left+right)/2;

        适合:left+right不会超过两者的数据类型。

        不适合:left+right相加超过当前数据类型,有负数出现两者相加变成0取整的情况。

2.mid=left+(left-right)/2;(比较推荐)

        适合:left-right不会超过两者的数据类型(right是负数时候可能会超过)。

        不适合:left-right超过当前的数据类型。

3.mid=(left+right)>>1;

        适合:left+right不会超过两者的数据类型。

        不适合:left+right相加超过当前数据类型。

提示:谨慎使用(left+right)/2,"/"除号为向0取整,如果是正数时候就为向下取整,取的是前驱,如果是负数时候就是向上取整,取的是后继,会计算有误,而其他两种不会出现这种情况,第二种方式所有情况为前驱,第三种方式所有情况为后继。

③mid的前驱或者后继

        前驱与后继的区别

        (1)前驱:当x存在时优先找第一个x,当x不存在时候首先找最大比x小的数。

        (2)后继:当x存在时优先找最后一个x,当x不存在时候首先找最小比x大的数。

前驱与后继的选择:对于单调不减的数据来说,需要数据尽可能小就选择前驱,需要数据尽可能大则后继。

        以上代码为后继的格式:特征有mid为向下取整,mid更加靠近左数值(left)。

前驱反之,代码如下:

 int all[n]
 int x;
 int left=0,right=n;
 while(left<right){
     int mid=left+(left-right+1)/2;//向上取整
     if(a[mid]<=x) left=mid;//相等时候取left先
     else right=mid-1;
 }
 return left;

二、前驱例题:

[P1873 COCI2011-2012#5] EKO / 砍树 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

 #include<iostream>
 using namespace std;
 const int N=1e6+10;
 int all[N];
 int cs,xq,ma=0;
 bool check(int mid){
     int tmp=0;
     for(int x=1;x<=cs;x++){
         if(all[x]-mid>0)tmp+=all[x]-mid;
         if(tmp>=xq)return true;
     }
     return false;
 }
 int main(){
 ​
     cin>>cs>>xq;
     for(int x=1;x<=cs;x++){
         cin>>all[x];
         if(all[x]>ma)ma=all[x];
     }
     int l=0,r=ma+1;
     while(l<r){
         int mid=l+(r-l+1)/2;
         if(check(mid)){
             l=mid;
         }
         else{
             r=mid-1;
         }
     }
     cout<<l;
 }

        选择前驱原因:题目要求数据尽可能地小(例如:1,2,3都符合条件,优先选择1)所以选择前驱的思路。


三、后继例题:

 #include<iostream>
 using namespace std;
 #include<algorithm>
 const int N=1e5+10;
 int cs,css,mi=0x3f3f3f3f,ma;
 long long res;
 int all[N];
 int main(){
     cin>>cs>>css;
     for(int x=1;x<=cs;x++){
         cin>>all[x];
         if(all[x]<mi)mi=all[x];
         if(all[x]>ma)ma=all[x];
     }
     sort(all+1,all+1+cs);
     for(int x=1;x<=css;x++){
         int l=1,r=cs+1;
         int num;
         cin>>num;
         while(l<r){
             int mid=l+(r-l-1)/2;
             if(all[mid]>=num){
                 r=mid;
             }
             else{
                 l=mid+1;
             }
         }
         int ql=0;
         if(l!=1)ql=min(abs(all[l]-num),abs(all[l-1]-num));
         else ql=abs(all[l]-num);
         res+=ql;
     }
     cout<<res;
 }

        该题前驱后继都可以实现的,为了凑数特地写了一个后继的。


(2)STL的upper_bound()和lowber_bound()

①upper_bound()

        —查找第一个大于x的。(如果有x则其前一个为最后一个x)

②lowber_bound()

        —查找第一个等于x,如果没有则返回比x大的最小值。(如果有x则其前一个是比x小的最大值)


使用方式:

        upper_bound(all+1,all+cs+1,x)

        检索从all[1]到all[cs]中是否存在比x,如果有则返回其地址。

使用代码如下:

 #include<iostream>
 #include<algorithm>
 using namespace std;
 int main(){
     int all[11];
     for(int x=1;x<=10;x++)all[x]=x*2+1;
     for(int x=1;x<=10;x++)cout<<all[x]<<" ";
     //数组为3 5 7 9 11 13 15 17 19 21
     cout<<endl;
     
     int* p1=upper_bound(all+1,all+10,6);
     cout<<"6后面第一个数为"<<*p1<<endl; 
     //6后面第一个数为7
     int* p2=upper_bound(all+1,all+10,7);
     cout<<"7后面第一个数为"<<*p2<<endl;
     //7后面第一个数为9
     
     int p3=lower_bound(all+1,all+10,9)-all;  //地址相减返回其下标 
     cout<<"9的下标为"<<p3<<endl;
     //9的下标为4
     int p4=lower_bound(all+1,all+10,10)-all;
     cout<<"比10大的最小数下标是"<<p4;
     //比10大的最小数下标是5
 }

有一定思路了趁热打铁刷刷题:【算法1-6】二分查找与二分答案 - 题单 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

  • 18
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值