二分问题
有单调性一定可以二分,但用二分的题不一定有单调性。
所以二分的本质并不是单调性,而是求边界的问题。
例题:数的范围
这个题目很适合理解二分的边界问题。
题目要求很简单,就是输入n个数字和m个查询。(前提是数组升序)
输出每一次查询的数在数组中的左右边界,如果不存在就输出 “
−
1
−
1
-1 -1
−1−1”
y总用了一个词我印象很深,就是“设定”。
所谓设定,就是每次找左右边界的时候,其实就是认为给了设定去数组里找。
- 比如,要找左边界,那我们就设定去答案里找 ≥ x \ge x ≥x的第一个元素。如果找到的元素 = x = x =x,那皆大欢喜;如果找到的元素不是x,那就是 > x >x >x的第一个元素。
- 同理,右边界,那我们就设定去答案里找 ≤ x \le x ≤x的最后一个元素。如果找到的元素 = x = x =x,那皆大欢喜;如果找到的元素不是x,那就是 < x <x <x的最后一个元素。
另外:要注意一点的是,如果是找左边界的时候,要取 m i d = l + r + 1 2 mid = \frac{l+r+1}{2} mid=2l+r+1 。 因为除法是向下取整,如果 l = r + 1 l = r+1 l=r+1时,就永远更新到 l l l ,进入死循环。
每一个二分算法都会有解,结束都是
l
=
r
l=r
l=r
但是具体有没有解的说法是题目的定义给出的。
# include<iostream>
using namespace std;
const int N = 100010;
int q[N], n, m, x;
int main(){
scanf("%d%d", &n, &m);
for(int i=0 ;i<n;i++)scanf("%d", &q[i]);
while(m--){
scanf("%d", &x);
int l = 0, r = n-1;
while(l<r){
int mid = l+r>>1;
if(q[mid]>=x) r = mid;
else l = mid + 1;
}
// 因为设定的条件是>=x的第一个数,因此当不存在x,则返回第一个大于x的数
if(q[l] != x) cout << "-1 -1"<<endl;
else{
cout << l <<" ";
l = 0, r = n-1;
while(l<r){
int mid = l + r + 1 >> 1;
if(q[mid]<=x) l = mid;
else r = mid - 1;
}
cout << l << endl;
}
}
return 0;
}
例题:大于等于x的最小值
通过上述分析就很明显了,就是求右边界,如果求到的不是 x x x ,那么输出下一位即可。如果下一位已经出界,那就输出 -1 。
而且这个还包括一个排序的过程,采用快排的方式。
# include<iostream>
using namespace std;
const int N = 10010;
int q[N], n, m, x;
void quickSort(int l, int r){
if(l>=r) return;
int pivot = q[(l+r)>>1];
int i = l-1, j = r+1;
while(i<j){
do i++; while(q[i]<pivot);
do j--; while(q[j]>pivot);
if(i<j)swap(q[i], q[j]);
}
quickSort(l, j);
quickSort(j+1, r);
}
int main(){
scanf("%d%d", &n, &m);
for(int i=0;i<n;i++)scanf("%d", &q[i]);
quickSort(0, n-1);
while(m--){
scanf("%d", &x);
int l = 0, r = n-1;
while(l < r){
int mid = (l+r+1) >> 1;
if(q[mid]<=x) l = mid;
else r = mid - 1;
}
if(q[l]!=x){
if( l+1<n )cout<<q[l+1]<<endl;
else cout<<"-1"<<endl;
}
else cout << x << endl;
}
return 0;
}