问题

问题描述

有一个有序序列a1,a2,a3,,ana_1,a_2,a_3,\cdots ,a_n, 问第一个大于kk的元素与位置是哪里.

样例输入

  • 第一行两个整数n(105),m(105)n(\leqslant 10^5),m(\leqslant 10^5),表示序列元素的数量,和有多少询问
  • 第二行,nn个数表示序列的元素,保证有序
  • 接下来mm行,询问的值
5 3
1 2 5 9 100
2
3
20

样例输出

  • 每行两个数,表示每个询问的第一个\geqslant的值和对应的位置
2 2
5 3
100 5

解析1: 暴力

本问题是:在一个单调序列上查找xxxx的后缀(当xx不存在时)

显示对于每个询问,最简单的方法就是使用暴力查询,代码如下

cpp
copy
        
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
#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)O(n^2),显然会超时.

解析2: 二分法

面对样例1,2,5,9,1001,2,5,9,100,这个序列是有序的

我们可以利用元素有序这条性质来加快我们的查询呢?

一个朴素的想法如下

面对样例,我们查询的位置的范围是[1,6][1,6],为什么最后一个位置是66,不应该是55吗?因为我们有可能会查询到比最大值100100还要大的元素,例如,200200,那这个时候,我们应该返回的位置是66,100100位置的后面一个位置.

同样,我们可以这样思考,我们把原序列末尾添加一个无穷大的值,改成

元素1259100位置123456 \begin{array}{lcccccc} \text{元素}& 1&2&5&9&100&\infin \\ \hline \text{位置}& \tt{1}&\tt2&\tt3&\tt4&\tt5&\tt6 \end{array}

那么如果出现比最大的一个元素还大的查询,返回的位置就是n+1n+1,同样如果得到返回值为n+1n+1则知道,没有查询到需要的值,输出not found

如果需要查询的元素是66, 可以描述我们的问题为:f(1,6,6)f(1,6,6),表示在区间[1,6][1,6]上找到第一个6\geqslant 6位置,我们挑选到中间位置(1+6)/2=3(1+6) / 2 = 3,值为55,又发则元素565 \leqslant 6,所以55和左边的元素都是比66小,所以范围[1,3][1,3]是对我们无用的范围,那么新的问就变成了:f(4,6,6)f(4,6,6),在区间[4,6][4,6]上查询第一个6\geqslant 6的位置

  • 我们成功的将问题缩小了一半,将问题变得到更简单了
  • 新的问题与原问题是相似的,这显然是递归
  • 小朋友法:如果找到足够多的小朋友来帮我们解题,一定可以解出来.所以问题有解.

下面采用更抽象的方法,来思考问题,设问题为f(l,r,val)f(l,r,val),表示在区间[l,r][l,r]上第一个val\geqslant val的位置 .条件:保证区间[l,r][l,r]上必然存在一个值val\geqslant val

则得到公式如下

f(l,r,val)={f(l,mid,val),a[mid]>=valf(mid+1,r,val),a[mid]<val(1) f(l,r,val) = \begin{cases} f(l,mid,val) ,& a[mid] >= val\\ f(mid+1,r,val) ,& a[mid] < val \end{cases} \tag1

其中mid=(l+r)÷2mid = \lfloor(l+r) \div 2\rfloor,且显然f(l,l,val)=lf(l,l,val) = l

于是我们写出如下代码:

cpp
copy
        
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
#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; //这是最快的写法 } //检查pos位置的值是否符合要求 bool check(int pos,int val){ return a[pos] >= val; } //bs_find = binary search find 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,,anA = a_1,a_2,\cdots,a_n上,有一个函数叫做check(ai)check(a_i),针对序列AA形成的新的序列B=b1,b2,,bnB = b_1,b_2,\cdots,b_n,其中bi=check(ai){true,false}b_i = check(a_i) \in \{true,false\},且序列bb的相同值的位置都是连续的,如下所示:

b1,b2,b3,,bmcheck=false,bm+1,,bncheck=true \color{blue}{ \underbrace{b_1,b_2,b_3,\cdots,b_m}_{check = false}}, \color{red}{ \underbrace{b_{m+1},\cdots,b_n}_{check = true} }

得到一个类似的如下的图:

如果序列b满足两个条件

  • 只有两个值
  • 相同值的位置都是连续

则说明序列AA在函数checkcheck是具有二分性.其中bm,bm+1b_m,b_{m+1}称为分界点

引理11 二分性等价定义1

一个具有二分性的序列不会出现蓝红蓝红蓝红的连续三个相邻值

证明:

必要性显然.充分性: 根据描述,序列最多出现蓝红红蓝,这种情况可以是边界,其它地方要么蓝,要么红,符合二分性的定义.

引理22 二分性等价定义2

一个序列AA符合下面两条性质,则序列AA与二分性等价.

  1. 若某个位置ii,则所有小于位置ii的都是
  2. 若某个位置ii,则所有大于位置ii的都是

证明:

必要性显然. 充分性: 使用反证法: 假设序列AA不符合引理11的描述,也就是会出现蓝红蓝的情况,但此时违反了引理22的描述,所以序列AA符合引理11,引理22可以推出引理11.证毕.

推论1

具有二分性的序列可以使用二分查找算法来查找分界点的位置

推论2

对于序列AA,若函数f(ai)f(a_i)成立,则f(aj),jif(a_j),j \geqslant i都成立,则会序列AA满足二分性.

推论2证明:

二分查找的正确性证明

开始证明:Proof.\cal{Proof}.

使用归纳法,证明bs_find(l,r,val)代码的正确性

只有1个元素的时候a1a_1,需要创建一个元素a2=a_2 = \infin, 问题就变成求f(1,2,val)f(1,2,val),取m=(1+2)1==1m = (1+2) \gg 1 == 1

分类讨论如下

  1. 情况1: check(1,val)=true    a1valcheck(1,val) = true \iff a_1 \geqslant val,check函数成立,则问题变成f(1,1,val)==1f(1,1,val) == 1,得到正确答案
  2. 情况2: check(1,val)=false    a1valcheck(1,val) = false \iff a_1 \ngeqslant val,check函数不成立,则问题变成f(2,2,val)==2f(2,2,val) == 2,得到正确答案

结论:当只有一个元素a1a_1时,函数bs_find可以得到正确答案

有两个元素ai,ai+1a_i,a_{i+1},且保证check(i+1,val)=truecheck(i+1,val) = true成立,取m=(i+(i+1)/2=im = (i+(i+1) / 2 = i,说明当两个相当位置i,i+1i,i+1,进行mid函数的运算后一定得到ii,也就是较小的那个位置.

分类讨论如下

  1. 情况1: check(m=i,val)=true    aivalcheck(m=i,val) = true \iff a_i \geqslant val,check函数成立,则问题变成f(i,i,val)==if(i,i,val) == i,得到正确答案
  2. 情况2: check(m=i,val)=false    aivalcheck(m=i,val) = false \iff a_i \ngeqslant val,check函数不成立,则问题变成f(i+1,i+1,val)==i+1f(i+1,i+1,val) == i+1,得到正确答案

结论:当只有两个元素ai,ai+1a_i,a_{i+1}时,且check(i+1,val)=truecheck(i+1,val)=true成立时,函数bs_find可以得到正确答案

同理,有33个元素:ai,ai+1,ai+1a_i,a_{i+1},a_{i+1},且保证check(i+2,val)=truecheck(i+2,val)=true成立时,函数bs_find(i,i+2,val)可以得到正确的答案

2k2k(偶数)个元素的时候a1,a2,,a2ka_1,a_2,\cdots,a_{2k}, 且保证check(2k,val)=truecheck(2k,val)=true成立时,取m=(1+2k)1=km = (1+2k) \gg 1 = k

分类讨论如下

  1. 情况1: check(m=k,val)=true    akvalcheck(m=k,val) = true \iff a_k \geqslant val,check函数成立,则问题变成f(1,k,val)f(1,k,val),
  2. 情况2: check(m=k,val)=false    akvalcheck(m=k,val) = false \iff a_k \ngeqslant val,check函数不成立,则问题变成f(k+1,2k,val)f(k+1,2k,val)

于是问题就变成是一个长度为kk的子问题,问题正确性由子问题的正确性保证

同理,当有2k+12k+1(奇数)个元素的时候a1,a2,,a2ka_1,a_2,\cdots,a_{2k}, 且保证check(2k+1,val)=truecheck(2k+1,val)=true成立时,取m=(1+2k+1)1=k+1m = (1+2k+1) \gg 1 = k+1,问题就变成是一个长度为k+1k+1,或kk的子问题,问题正确性由子问题的正确性保证

这样一直到分解,最后剩余的元素个数,一定会变成2233,而这两个长度,已经验证成功.

证明完毕.Q.E.D\cal{Q.E.D}

一般化

TODO 没有解释清楚

具体什么样的情况下可以使用二分查询算法呢?

在一个序列A=a1,a2,,anA = a_1,a_2,\cdots,a_n上,有一个函数叫做check(ai)check(a_i),针对序列AA形成的新的序列B=b1,b2,,bnB = b_1,b_2,\cdots,b_n,其中bi=check(ai){true,false}b_i = check(a_i) \in \{true,false\}

序列

b1,b2,b3,,bmcheck=false,bm+1,,bncheck=true \color{blue}{ \underbrace{b_1,b_2,b_3,\cdots,b_m}_{check = false}}, \color{red}{ \underbrace{b_{m+1},\cdots,b_n}_{check = true} }

得到一个类似的如下的图:

若序列 存在某个位置

什么是单调性?

单调性的性质:

在一个单调性的序列上

  1. 查询使函数check(pos)check(pos)第一个成立的位置
  2. 查询使函数check(pos)check(pos)最后一个失败的后一个的位置

可以使用二分查询法

什么题目使用

  1. 第一个x\geqslant x元素的位置
  2. 第一个=x= x元素的位置
  3. 最后一个x\leqslant x元素的位置
  4. 求解数字xx的数量
  5. 求解满足数字val[l,r]val \in [l,r]的数量

经典模型

  • 最小值最大,最大值最小问题
    • 1.序列划分

练习题目

  • luogu P1083
  • luogu P2678
  • luogu P1314
  • luogu P1868
  • luogu P1493
  • hdu 6231
  • poj 3273
  • poj 3258
  • poj 1905
  • poj 3122
  • luogu P1419 二分+单调队列