二分思想及其应用
这一篇随笔适用于对二分有一定了解,希望有更多练习的OI选手或是计算机专业学生。
相信学过OI或是计算机专业的同学一定对此不陌生。二分思想充斥在许多算法与数据结构中。作为许多高级算法的基础,二分在计算机的思想中起到一个十分重要的作用。
二分思想的核心是分而治之。一个似乎不太好解的题,想一想二分,寻找两边的子问题的解,不断下沉,便能缩小范围,简化问题。
想想这些问题你会怎么解?
- 在有序数组中寻找某一个确定值的位置
- 求解数组中逆序对问题
- 寻找函数的零点
- 在所有出现的最大情况中,寻找最小值
二分的常见算法是二分查找和二分答案,接下来我将重点讨论两种二分查找实现:
对于第一个问题,我们有一个变式,数组中的值可重复,需要你找到数组中的数的下标尽量小(如果存在的话)
如数组a[6]={0,1,1,3,4,6}a[6]=\{0,1,1,3,4,6\}a[6]={0,1,1,3,4,6},那么如果我们需要寻找 111 ,那么我们需要输出结果为 222 ,原因是出现1的位置最小在第 222 位(还有一个是第 333 位)。
给出两种二分的模板
左偏二分
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;
}
通过演算(需要自己在草稿纸上进行模拟),我们会发现:
- 两个二分的 l,rl,rl,r 均会在最后相等,得到一个值;(结论一)
- 在左偏二分中,值会偏向更小的位置;右偏二分则相反; (结论二)
- 如果数组中不存在值 kkk ,那么左偏二分将偏向比 kkk 大的最小的值,而右偏二分将偏向比 kkk 小的最大值。 (结论三)
具体分析方法可以参考博主往期codeforces题解。
因此,在不同的题中,我们应该根据情况合理选择两个二分。在某些题中甚至需要同时用上两个二分!
题目示例
题目来源:SUSTech_DSAA_lab2_T6
题意
给出一个N∗N(1≤N≤50000)N*N(1\le N\le 50000)N∗N(1≤N≤50000)的矩阵,满足矩阵中第 iii 行第 jjj 列的值为
f[i][j]=i2+j2+12345∗i−12345∗j+i∗j
f[i][j] = i^2+j^2+12345*i-12345*j+i*j
f[i][j]=i2+j2+12345∗i−12345∗j+i∗j
求第 M(1≤M≤N∗N)M(1\le M \le N*N)M(1≤M≤N∗N) 小的值。
分析
相信大多数同学会想到使用自己的聪(chong)明(dong)的脑袋瓜子,觉得这道题是一个找规律题(
的确,在 N≤65N\le65N≤65 之前,会发现:好像是有排序规律的诶。
11 07 04 02 0116 12 08 05 0320 17 13 09 0623 21 18 14 1025 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矩阵示例
但实际上,对于一个这样的毫无规律可言的表达式,在 NNN 较大时就已经不适用了。
对表达式 f[i][j]=i2+j2+12345∗i−12345∗j+i∗jf[i][j] = i^2+j^2+12345*i-12345*j+i*jf[i][j]=i2+j2+12345∗i−12345∗j+i∗j 稍加分析,我们能够发现:
- f[i][j]>f[i−1][j−1]f[i][j]> f[i-1][j-1]f[i][j]>f[i−1][j−1] (无效结论,但经常被拿来使用,这也是我为什么卡了很久的原因)
- 固定 jjj 不变,当 iii 增大时,求导为 f′=2i+12345+j>0f'=2i+12345+j>0f′=2i+12345+j>0 ,fff 增大;
- 固定 iii 不变,当 jjj 增大时,求导为 f′=2j−12345+if'=2j-12345+if′=2j−12345+i ,f′f'f′的大小和 i,ji,ji,j 均有关系,但基本可确定为先减后增;
观察结论二,实际上便可得到我们的解法:
- 在矩阵的同一列中,这一列数字是有序递增的,在这一列中我们可以使用二分思想;
- 对于每一列,都进行二分;
- 整合,求解。
很明显,我们想找到第 MMM 小的值,但我们无法知道自己所求解的东西是什么。于是,我们可以反过来:
- 假定一个值 kkk ,这是我们假定的答案;
- 实行上述的1,2,3操作,求得在答案k的情况下,比k小的值的个数为 mmm (对于每一行都是如此);
- 将 mmm 与 MMM 进行比较,如果 mmm 更大则减小 kkk 的值,如果 mmm 更小则增大 kkk 的值,如果相等呢?
这里相信细心的同学已经发现了, 1,2,3步执行的是二分查找的工作,4,5,6步执行的是二分答案的操作。并且在这里涉及到了一个问题:如果有相等的数应该偏左还是偏右?如果 M=mM=mM=m 有应该取左还是取右?
让我们进行一下手动模拟的操作:
- 对于每一列的二分查找,我们希望找到小于等于k的数的个数。如果没有数与 kkk 相等,则 midmidmid 应该获取比它更小一点的数的位置。由此分析,应选择右偏二分;
- 对于整个统筹的二分答案,如果 M=mM=mM=m ,则应该考虑更小的 k 值(想一想,为什么?),由此应选择左偏二分;
因此,我非常推荐大家以此题来测试自己对二分的熟练度,它很好的将两种二分结合起来,其总复杂度为O(nlog2n)O(nlog^2n)O(nlog2n)
后话
其实,在具体实现的过程中,大家会发现,矩阵中会存在两个相同的数字,因此,在较大的相邻两位可能答案是相同的。而我们的二分查找实际上会偏向更大的值(好比第233位和第234位都是123456,但我们在假定答案位123456时,得到的 mmm 一定为234),为了防止跳过正确答案,在输入为233时,我们应该偏向右边的234位(在二分答案中,不会出现 m=233m=233m=233 这个答案,因此我们应该找相对更大一点的值234,这也是为什么二分答案选择右偏的原因之一)。
本文讲解了如何运用二分思想在有序数组中查找特定值的最小下标,逆序对问题,函数零点搜索,以及如何结合左偏和右偏二分查找解决矩阵最小值问题。通过实例分析,展示了二分查找和二分答案在实际问题中的应用,包括SUSTech DSAA_lab2_T6题目的解法。

3291

被折叠的 条评论
为什么被折叠?



