问题
问题描述
有一个有序序列a1,a2,a3,⋯,an, 问第一个大于k的元素与位置是哪里.
样例输入
- 第一行两个整数n(⩽105),m(⩽105),表示序列元素的数量,和有多少询问
- 第二行,n个数表示序列的元素,保证有序
- 接下来m行,询问的值
5 3
1 2 5 9 100
2
3
20
样例输出
- 每行两个数,表示每个询问的第一个⩾的值和对应的位置
解析1: 暴力
本问题是:在一个单调序列上查找x或x的后缀(当x不存在时)
显示对于每个询问,最简单的方法就是使用暴力查询,代码如下
#include <iostream>
using namespace std;
const int maxn=qe5+5;
int a[maxn];
int n,m;
int find(int val) {
for (int i = 1; i <= n; i++)
{
if (a[i] >= val)
return i;
}
return n+1;
}
int main() {
cin >> n >> m;
for(int i =1;i<=n;i++ )
cin >> a[i];
for(int i =1;i<=m;i++) {
int num;
cin >> num;
int pos = find(num);
if( pos == n+1)
cout << "not found\n";
else
cout << a[pos] << " " << pos << "\n";
}
return 0;
}
上面的代码的时间复杂度为O(n2),显然会超时.
解析2: 二分法
面对样例1,2,5,9,100,这个序列是有序的
我们可以利用元素有序这条性质来加快我们的查询呢?
一个朴素的想法如下
面对样例,我们查询的位置的范围是[1,6],为什么最后一个位置是6,不应该是5吗?因为我们有可能会查询到比最大值100还要大的元素,例如,200,那这个时候,我们应该返回的位置是6,100位置的后面一个位置.
同样,我们可以这样思考,我们把原序列末尾添加一个无穷大的值,改成
元素位置112253941005∞6
那么如果出现比最大的一个元素还大的查询,返回的位置就是n+1,同样如果得到返回值为n+1则知道,没有查询到需要的值,输出not found
如果需要查询的元素是6,
可以描述我们的问题为:f(1,6,6),表示在区间[1,6]上找到第一个⩾6位置,我们挑选到中间位置(1+6)/2=3,值为5,又发则元素5⩽6,所以5和左边的元素都是比6小,所以范围[1,3]是对我们无用的范围,那么新的问就变成了:f(4,6,6),在区间[4,6]上查询第一个⩾6的位置
- 我们成功的将问题缩小了一半,将问题变得到更简单了
- 新的问题与原问题是相似的,这显然是递归
- 小朋友法:如果找到足够多的小朋友来帮我们解题,一定可以解出来.所以问题有解.
下面采用更抽象的方法,来思考问题,设问题为f(l,r,val),表示在区间[l,r]上第一个⩾val的位置 .条件:保证区间[l,r]上必然存在一个值⩾val
则得到公式如下
f(l,r,val)={f(l,mid,val),f(mid+1,r,val),a[mid]>=vala[mid]<val(1)其中mid=⌊(l+r)÷2⌋,且显然f(l,l,val)=l
于是我们写出如下代码:
#include <iostream>
using namespace std;
const int maxn=1e5+5;
int a[maxn];
int n,m;
int mid(int l,int r) {
return (l+r) >> 1;
}
bool check(int pos,int val){
return a[pos] >= val;
}
int bs_find(int l,int r,int val) {
while( l < r) {
int m = mid(l,r);
if( check(m,val))
r = m;
else
l = m+1;
}
return l ;
}
int main() {
cin >> n >> m;
for(int i =1;i<=n;i++ )
cin >> a[i];
for(int i =1;i<=m;i++) {
int num;
cin >> num;
int pos = bs_find(1,n+1,num);
if( pos == n+1)
cout << "not found\n";
else
cout << a[pos] << " " << pos << "\n";
}
return 0;
}
总结
定义1 二分性
在一个序列A=a1,a2,⋯,an上,有一个函数叫做check(ai),针对序列A形成的新的序列B=b1,b2,⋯,bn,其中bi=check(ai)∈{true,false},且序列b的相同值的位置都是连续的,如下所示:
check=falseb1,b2,b3,⋯,bm,check=truebm+1,⋯,bn得到一个类似的如下的图:

如果序列b满足两个条件
则说明序列A在函数check是具有二分性.其中bm,bm+1称为分界点
引理1 二分性等价定义1
一个具有二分性的序列不会出现蓝红蓝或红蓝红的连续三个相邻值
证明:
必要性显然.充分性: 根据描述,序列最多出现蓝红或红蓝,这种情况可以是边界,其它地方要么蓝,要么红,符合二分性的定义.
引理2 二分性等价定义2
一个序列A符合下面两条性质,则序列A与二分性等价.
- 若某个位置i是
蓝,则所有小于位置i的都是蓝
- 若某个位置i是
红,则所有大于位置i的都是红
证明:
必要性显然. 充分性: 使用反证法: 假设序列A不符合引理1的描述,也就是会出现蓝红蓝的情况,但此时违反了引理2的描述,所以序列A符合引理1,引理2可以推出引理1.证毕.
推论1
具有二分性的序列可以使用二分查找算法来查找分界点的位置
推论2
对于序列A,若函数f(ai)成立,则f(aj),j⩾i都成立,则会序列A满足二分性.
推论2证明:
二分查找的正确性证明
开始证明:Proof.
使用归纳法,证明bs_find(l,r,val)代码的正确性
只有1个元素的时候a1,需要创建一个元素a2=∞, 问题就变成求f(1,2,val),取m=(1+2)≫1==1
分类讨论如下
- 情况1: check(1,val)=true⟺a1⩾val,
check函数成立,则问题变成f(1,1,val)==1,得到正确答案
- 情况2: check(1,val)=false⟺a1val,
check函数不成立,则问题变成f(2,2,val)==2,得到正确答案
结论:当只有一个元素a1时,函数bs_find可以得到正确答案
有两个元素ai,ai+1,且保证check(i+1,val)=true成立,取m=(i+(i+1)/2=i,说明当两个相当位置i,i+1,进行mid函数的运算后一定得到i,也就是较小的那个位置.
分类讨论如下
- 情况1: check(m=i,val)=true⟺ai⩾val,
check函数成立,则问题变成f(i,i,val)==i,得到正确答案
- 情况2: check(m=i,val)=false⟺aival,
check函数不成立,则问题变成f(i+1,i+1,val)==i+1,得到正确答案
结论:当只有两个元素ai,ai+1时,且check(i+1,val)=true成立时,函数bs_find可以得到正确答案
同理,有3个元素:ai,ai+1,ai+1,且保证check(i+2,val)=true成立时,函数bs_find(i,i+2,val)可以得到正确的答案
有2k(偶数)个元素的时候a1,a2,⋯,a2k, 且保证check(2k,val)=true成立时,取m=(1+2k)≫1=k
分类讨论如下
- 情况1: check(m=k,val)=true⟺ak⩾val,
check函数成立,则问题变成f(1,k,val),
- 情况2: check(m=k,val)=false⟺akval,
check函数不成立,则问题变成f(k+1,2k,val)
于是问题就变成是一个长度为k的子问题,问题正确性由子问题的正确性保证
同理,当有2k+1(奇数)个元素的时候a1,a2,⋯,a2k, 且保证check(2k+1,val)=true成立时,取m=(1+2k+1)≫1=k+1,问题就变成是一个长度为k+1,或k的子问题,问题正确性由子问题的正确性保证
这样一直到分解,最后剩余的元素个数,一定会变成2或3,而这两个长度,已经验证成功.
证明完毕.Q.E.D
一般化
TODO 没有解释清楚
具体什么样的情况下可以使用二分查询算法呢?
在一个序列A=a1,a2,⋯,an上,有一个函数叫做check(ai),针对序列A形成的新的序列B=b1,b2,⋯,bn,其中bi=check(ai)∈{true,false}
序列
check=falseb1,b2,b3,⋯,bm,check=truebm+1,⋯,bn得到一个类似的如下的图:

若序列
存在某个位置
什么是单调性?
单调性的性质:
在一个单调性的序列上
- 查询使函数check(pos)第一个成立的位置
- 查询使函数check(pos)最后一个失败的后一个的位置
可以使用二分查询法
什么题目使用
- 第一个⩾x元素的位置
- 第一个=x元素的位置
- 最后一个⩽x元素的位置
- 求解数字x的数量
- 求解满足数字val∈[l,r]的数量
经典模型
练习题目
- luogu P1083
- luogu P2678
- luogu P1314
- luogu P1868
- luogu P1493
- hdu 6231
- poj 3273
- poj 3258
- poj 1905
- poj 3122
- luogu P1419 二分+单调队列