以前电视上有一种节目,主持人给一种商品,让参赛者猜其价格,参赛者猜一次之后主持人会提示参赛者猜的价格是高了还是低了。
主持人会给一个价格区间,这时候如果你是参赛者你会怎么猜呢?从主持人给的价格开始猜吗?
这样的效率太低了,如果主持人给的区间是1-1000,这时候的复杂度就是O(n)
如果给我的话我会从主持人给的价格中位数开始猜测,我会从500元开始猜,然后主持人说这个是高了还是低了,高了我就猜250,低了我就猜750,。
这个时候我用的方法就是二分。
首先我先来介绍整数的二分查找:
题目描述
给定一个按照升序排列的长度为 n 的整数数组,以及 q 个查询。
对于每个查询,返回一个元素 k 的起始位置和终止位置(位置从 0 开始计数)。
如果数组中不存在该元素,则返回 -1
。
输入格式
第一行包含整数n 和 q,表示数组长度和询问个数。
第二行包含 n 个整数(均在 1~10000 范围内),表示完整数组。
接下来 q 行,每行包含一个整数 k,表示一个询问元素。
输出格式
共 q 行,每行包含两个整数,表示所求元素的起始位置和终止位置。
如果数组中不存在该元素,则返回 -1
。
数据范围
1 ≤ n ≤ 100000
1 ≤ q ≤ 10000
1≤ k ≤ 10000
输入样例:
6 3
1 2 2 3 3 4
3
4
5
输出样例:
3 4
5 5
-1 -1
-
int binary_search1(int l,int r) { while(l<r) { int mid = (l+r+1)/2; if(check(mid)) l = mid; else r = mid-1; } return l; }
另一种情况和上面这种很相似
不过区别是mid = (l+r)/2
if( check( mid ) ) r = mid ;
else l = mid +1 ;
int binary_search2(int l,int r) { while(l<r) { int mid = (l+r)/2; if(check(mid)) r = mid; else l = mid+1; } return l; }
再回到这题
这题是单调上升的序列
我们可以把答案区间[ l , r ] 左边 [ 0, r ] 看成是 <= x 的 区间
右边 [ l , n-1] 看成是 >= x 的区间
那么我们只要两次二分先找到l再找到r就可以了
# include <iostream> # include <cstdio> using namespace std; const int N=100010; int n,m; int a[N]; int main() { cin>>n>>m; for(int i=0;i<n;i++) scanf("%d",&a[i]); while(m--) { int k; cin>>k; int l=0,r=n-1; while(l<r) { int mid=(l+r)/2; if(a[mid]>=k) r=mid; else l=mid+1; } if(a[l]!=k) cout<<"-1 -1"<<endl; //如果左边界没有找到说明区间里面没有这个数 else { cout<<l<<" "; r=n-1; //重新给右区间赋值 while(l<r) { int mid=(l+r+1)/2; //一定要注意这里的+1 死记住就可以了 if(a[mid]<=k) l=mid; else r=mid-1; } cout<<r<<endl; } } }
下面就介绍一下浮点数的二分,相对于整数二分来说反而简单许多
题目描述
给定一个浮点数 n,求它的三次方根。
输入格式
共一行,包含一个浮点数 n。
输出格式
共一行,包含一个浮点数,表示问题的解。
注意,结果保留 6 位小数。
数据范围
-10000 ≤ n ≤ 10000
输入样例:
1000.00
输出样例:
10.000000
这里由于答案只有一个,所以你用binary_search1()或者 binary_search2()都可以
而且对于mid 都取 (l+r)/2
# include <iostream> # include <algorithm> # include <cstdio> using namespace std; double n; int main() { cin>>n; double l = -10000.0,r=10000.0; while(r - l > 1e-8) //小技巧,根据精度来确定循环条件 { double mid = (l+r) /2 ; if(mid*mid*mid >= n) r=mid; else l = mid; } printf("%.6f",l); //注意是输出6位小数 }