目录
(2)STL的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)