二分
二分是一种基于一个具有非严格单调性的序列上进行搜索的算法,其复杂度为 \(O(logn)\),在单调性的前提下,效率碾压遍历不知道多少倍。
原理
我们以下图中长度 \(n=10\) 的非严格单调递增序列 \(P\) 为例
假设此时我们询问一个数 \(x\),我们需要在序列中找到一个数 \(y\) ,使得 \(y\) 是整个序列中所有大于等于 \(x\) 的树中最小的,即 \(y=\min\left \{ i \right \}[i \in P ,i \ge x]\)。
现在思考如何找到这个数 \(y\) 。
一种极其明显的思路是,从 \(i=1\) 处开始遍历至 \(i=10\) 处,遍历过程中实时维护变量 \(sum\) 。
for(int i=1;i<=10;i++)
{
if(P[i]>=x) sum=min(P[i],sum);
}
当然由于序列 \(P\) 单调递增,你完全可以在找到第一个大于等于 \(x\) 的数后立刻退出循环,但是尽管如此也不能改变复杂度为 \(O(n)\) 的事实。
如果我们想 \(O(logn)\) 地找 \(y\) 怎么办?
二分!
现在我们定义两个指针变量 \(L=1,R=10\),代表此时答案会包含在此区间内,同时算出区间 \([L,R]\) 的中点 \(mid\) (向下取整),如图
此时我们判断 \(mid\) 所在值 \(P[mid]\) 是否满足大于等于 \(x\) ,若满足,则根据序列 \(P\) 单调递增的性质,可得大于 \(x\) 中最小的树必定在区间 \([mid,R]\) 中,此刻我们更新 \(L=mid\),并进入下一次二分
若不满足,则答案必定位于区间 \([L,mid-1]\),为什么是 \(mid-1\)?,因为值 \(P[mid]\) 并不满足我们的条件,因此在下一次二分时,我们的答案区间不能包含 \(mid\)。此时更新 \(R=mid-1\)。
那么我们如何知道是否找到答案了呢?
在上面的过程可知,我们二分的区间总满足 \(L<R\) ,这是必然的,若 \(L=R\) 了,则证明二分结束,找到答案或答案不存在,因此我们要用一个while语句将二分过程囊括起来,而while的循环条件就设为 \(L<R\)。因为最终 \(L=R\) ,所以我们只需要输出 \(L\) 即可。且此刻 \(\forall i < mid\),有 \(P[i] < x\); \(\forall i \ge mid\),有 \(p[i] > x\)。
最终的答案会有三种情况:
- \(L\in [1,n]\)
证明答案存在
- \(L > n\)
证明答案不存在,且此时序列 \(P\) 中所有值都小于 \(x\)
- \(R = 1\)
此刻你需要判断 \(P[R]\) 是否大于等于 \(x\),以此判断答案的存在性。(事实上你可以在最初初始化指针变量 \(L,R\) 时把 \(L\) 设为 \(0\),最终答案不存在的情况就会变成 \(R=0\))
代码
二分有多种不同的模板,其区别大多仅在于循环条件与 \(mid\) 计算上,使用时需要根据具体情况合理修改条件即可。
以下是上面的例子的代码,也是最通用的二分模板
int L=0,R=n;
while(L<R)
{
int mid=(L+R)/2;//or mid=(L+R)>>1;
if(P[mid]>=x) L=mid;
else R=mid-1;
}
printf("%d",P[L]);
例题
(updating......)
标签:二分,mid,区间,答案,序列,单调 From: https://www.cnblogs.com/AnEasySong/p/er-fen.html