整数二分

本质:可以使区间被分为两部分,其中一部分满足性质A,另一部分不满足性质A,即可使用二分

模板一:取左部分的右端点

1
2
3
4
5
6
7
mid=(l+r+1)>>1;//若mid=l+r>>1,则当l=r-1时,mid=l,此时若check(mid)成立,则l,r不会变化,出现死循环
if(check(mid)){//判断mid是否满足左侧的要求
l=mid;//如果满足,则分界一定在mid及其右侧(可能是mid,所以l=mid)
}
else{
r=mid-1;//如果不满足,则分界一定在mid左侧(一定不是mid,所以r=mid-1)
}

模板二:取右部分的左端点

1
2
3
4
5
6
7
mid=l+r>>1;
if(check(mid)){//判断mid是否满足右侧的要求
r=mid;//如果满足,则分界一定在mid及其左侧(可能是mid,所以r=mid)
}
else{
l=mid+1;//如果不满足,则分界一定在mid右侧(一定不是mid,所以l=mid+1)
}

例题:输入一个排好序的数组,求给定数的起始位置和终止位置

分析:求起始位置可以把区间分为$<x,\ge x$两部分,取右部分的左端点;

​ 求终止位置可以把区间分为$\le x,>x$两部分,取左部分的右端点。

代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include<bits/stdc++.h>
using namespace std;

const int N=1e6;
int n,Q;
int q[N];

int find_l(int k){//求左端点
int l=1,r=n;
while(l<r){
int mid=l+r>>1;
if(q[mid]>=k){
r=mid;
}
else{
l=mid+1;
}
}
if(q[l]!=k)return 0;//若端点上的数不为要求的数,则这个数不存在
return l;//此时l=r,返回l,r均可
}

int find_r(int k){//求右端点
int l=1,r=n;
while(l<r){
int mid=l+r+1>>1;
if(q[mid]<=k){
l=mid;
}
else{
r=mid-1;
}
}
if(q[r]!=k)return 0;//若端点上的数不为要求的数,则这个数不存在
return r;//此时l=r,返回l,r均可
}

int main(){
cin >> n >> Q;
for(int i=1;i<=n;i++){
cin >> q[i];
}
for(int i=1;i<=Q;i++){
int x;
cin >> x;
cout << find_l(x)-1 << ' ' << find_r(x)-1 << endl;
}
return 0;
}