二分思想及其应用

二分思想及其应用

这一篇随笔适用于对二分有一定了解,希望有更多练习的OI选手或是计算机专业学生。

相信学过OI或是计算机专业的同学一定对此不陌生。二分思想充斥在许多算法与数据结构中。作为许多高级算法的基础,二分在计算机的思想中起到一个十分重要的作用。

二分思想的核心是分而治之。一个似乎不太好解的题,想一想二分,寻找两边的子问题的解,不断下沉,便能缩小范围,简化问题。

想想这些问题你会怎么解?

  1. 在有序数组中寻找某一个确定值的位置
  2. 求解数组中逆序对问题
  3. 寻找函数的零点
  4. 在所有出现的最大情况中,寻找最小值

二分的常见算法是二分查找和二分答案,接下来我将重点讨论两种二分查找实现:

对于第一个问题,我们有一个变式,数组中的值可重复,需要你找到数组中的数的下标尽量小(如果存在的话)

如数组 a [ 6 ] = { 0 , 1 , 1 , 3 , 4 , 6 } a[6]=\{0,1,1,3,4,6\} a[6]={0,1,1,3,4,6},那么如果我们需要寻找 1 1 1 ,那么我们需要输出结果为 2 2 2 ,原因是出现1的位置最小在第 2 2 2 位(还有一个是第 3 3 3 位)。

给出两种二分的模板

左偏二分

int l = 0, r = n - 1; //两个区间端点
while(l < r) {
    int mid = (l + r) / 2;
    if(a[mid] < k) l = mid + 1;
    else r = mid;
}

右偏二分

int l = 0, r = n - 1;
while(l < r) {
    int mid = (l + r) - (l + r) / 2;
    if(a[mid] <= k) l = mid;
    else r = mid - 1;
}

通过演算(需要自己在草稿纸上进行模拟),我们会发现:

  1. 两个二分的 l , r l,r l,r 均会在最后相等,得到一个值;(结论一)
  2. 在左偏二分中,值会偏向更小的位置;右偏二分则相反; (结论二)
  3. 如果数组中不存在值 k k k ,那么左偏二分将偏向比 k k k 大的最小的值,而右偏二分将偏向比 k k k 小的最大值。 (结论三)

具体分析方法可以参考博主往期codeforces题解。

因此,在不同的题中,我们应该根据情况合理选择两个二分。在某些题中甚至需要同时用上两个二分!

题目示例

题目来源:SUSTech_DSAA_lab2_T6

题意

给出一个 N ∗ N ( 1 ≤ N ≤ 50000 ) N*N(1\le N\le 50000) NN(1N50000)的矩阵,满足矩阵中第 i i i 行第 j j j 列的值为
f [ i ] [ j ] = i 2 + j 2 + 12345 ∗ i − 12345 ∗ j + i ∗ j f[i][j] = i^2+j^2+12345*i-12345*j+i*j f[i][j]=i2+j2+12345i12345j+ij
求第 M ( 1 ≤ M ≤ N ∗ N ) M(1\le M \le N*N) M(1MNN) 小的值。

分析

相信大多数同学会想到使用自己的聪(chong)明(dong)的脑袋瓜子,觉得这道题是一个找规律题(

的确,在 N ≤ 65 N\le65 N65 之前,会发现:好像是有排序规律的诶。
11   07   04   02   01 16   12   08   05   03 20   17   13   09   06 23   21   18   14   10 25   24   22   19   15 11\ 07\ 04\ 02\ 01\\ 16\ 12\ 08\ 05\ 03\\ 20\ 17\ 13\ 09\ 06\\ 23\ 21\ 18\ 14\ 10\\ 25\ 24\ 22\ 19\ 15\\ 11 07 04 02 0116 12 08 05 0320 17 13 09 0623 21 18 14 1025 24 22 19 15
​ 5*5矩阵示例

但实际上,对于一个这样的毫无规律可言的表达式,在 N N N 较大时就已经不适用了。

对表达式 f [ i ] [ j ] = i 2 + j 2 + 12345 ∗ i − 12345 ∗ j + i ∗ j f[i][j] = i^2+j^2+12345*i-12345*j+i*j f[i][j]=i2+j2+12345i12345j+ij 稍加分析,我们能够发现:

  1. f [ i ] [ j ] > f [ i − 1 ] [ j − 1 ] f[i][j]> f[i-1][j-1] f[i][j]>f[i1][j1] (无效结论,但经常被拿来使用,这也是我为什么卡了很久的原因)
  2. 固定 j j j 不变,当 i i i 增大时,求导为 f ′ = 2 i + 12345 + j > 0 f'=2i+12345+j>0 f=2i+12345+j>0 f f f 增大;
  3. 固定 i i i 不变,当 j j j 增大时,求导为 f ′ = 2 j − 12345 + i f'=2j-12345+i f=2j12345+i f ′ f' f的大小和 i , j i,j i,j 均有关系,但基本可确定为先减后增;

观察结论二,实际上便可得到我们的解法:

  1. 在矩阵的同一列中,这一列数字是有序递增的,在这一列中我们可以使用二分思想;
  2. 对于每一列,都进行二分;
  3. 整合,求解。

很明显,我们想找到第 M M M 小的值,但我们无法知道自己所求解的东西是什么。于是,我们可以反过来:

  1. 假定一个值 k k k ,这是我们假定的答案;
  2. 实行上述的1,2,3操作,求得在答案k的情况下,比k小的值的个数为 m m m (对于每一行都是如此);
  3. m m m M M M 进行比较,如果 m m m 更大则减小 k k k 的值,如果 m m m 更小则增大 k k k 的值,如果相等呢?

这里相信细心的同学已经发现了, 1,2,3步执行的是二分查找的工作,4,5,6步执行的是二分答案的操作。并且在这里涉及到了一个问题:如果有相等的数应该偏左还是偏右?如果 M = m M=m M=m 有应该取左还是取右?

让我们进行一下手动模拟的操作:

  1. 对于每一列的二分查找,我们希望找到小于等于k的数的个数。如果没有数与 k k k 相等,则 m i d mid mid 应该获取比它更小一点的数的位置。由此分析,应选择右偏二分;
  2. 对于整个统筹的二分答案,如果 M = m M=m M=m ,则应该考虑更小的 k 值(想一想,为什么?),由此应选择左偏二分;

因此,我非常推荐大家以此题来测试自己对二分的熟练度,它很好的将两种二分结合起来,其总复杂度为 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)

后话

其实,在具体实现的过程中,大家会发现,矩阵中会存在两个相同的数字,因此,在较大的相邻两位可能答案是相同的。而我们的二分查找实际上会偏向更大的值(好比第233位和第234位都是123456,但我们在假定答案位123456时,得到的 m m m 一定为234),为了防止跳过正确答案,在输入为233时,我们应该偏向右边的234位(在二分答案中,不会出现 m = 233 m=233 m=233 这个答案,因此我们应该找相对更大一点的值234,这也是为什么二分答案选择右偏的原因之一)。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值