一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第3天,点击查看活动详情。
二分的性质
有单调性就一定可以二分,如果没有单调性也可以二分
本质:左边满足性质,右边不满足,二分可以用来寻找满足的边界跟不满足的边界(针对整数二分)
分析出来有上面这种情况的:只有满足性质跟不满足性质两种,我们就要想到可以用二分来做
情况一(模板一)
二分红色的边界点
- check(mid)用来检测是否满足红颜色的条件
- true的条件下,注意:是包含mid的哦,因为mid满足红色的性质
- false的条件下,是不包含mid的,又因为整数二分,所以是mid - 1
mid = (l + r + 1)/2
情况二(模板二)
二分绿色的边界点
这里check(mid)用来检测是否满足绿颜色的条件
问题:如何选择用哪个模板?
- 其实不用过于纠结,方法如下
- 先写一个check函数, 也就是思考一下性质是什么
- 判断true或false的时候,对应的l跟r的变化,如果
true的情况下:- 区间需要更新为
l = mid,那么mid = (l + r + 1) / 2 - 区间需要更新为
r = mid,那么mid = (l + r) / 2
- 区间需要更新为
记忆小技巧:army:读音与r mid相似,军队理想状态下没有人牺牲,也就是不变,军队的决策正确(true),也就是mid 不用加一,如果false,l = mid + 1
为什么第一个模板mid要加上1
- 假设:l = r - 1 , mid 没有加1(mid是向下取整的)
- 第二步:true的情况下更新为l = mid , l 还是=l, 区域范围还是没有变化,就会陷入死循环
题目
找第一个x的位置
- 发现:check函数是
q[mid] >= x - 右边所有数都满足>= x这个性质,左边所有数都不满足>=x这个性质
找最后一个x的位置
- 发现:check函数是
q[mid] <= x - 左边所有数都满足<= x这个性质,右边所有数都不满足<=x这个性质
代码
#include <iostream>
using namespace std;
const int N = 100010;
int n, m;
int q[N];
int main()
{
scanf("%d%d", &n, &m);
for (int i = 0; i < n; i++) scanf("%d", &q[i]); // 将数组读进来
while (m --) // m个询问
{
int x;
scanf("%d", &x);
int l = 0, r = n - 1; // 从0开始呀
while(l < r)
{
int mid = l + r >> 1;
// check()函数
if (q[mid] >= x) r = mid;
else l = mid + 1;
}
// 二分出来的结果:第一个>= x的数,下面为不存在x的判断
if (q[l] != x) cout << "-1 -1" << endl;
else
{
cout << l << ' '; // 这里输出l跟输出r是一样的,因为while循环结束的时候l=r
// 寻找右边界
int l = 0, r = n - 1;
while (l < r)
{
int mid = l + r + 1>> 1;
if (q[mid] <= x) l = mid;
else r = mid - 1;
}
cout << l << endl;
}
}
}
总结
- 二分的模板是一定有解的,如果无解的话就是跟题目有关的,根据题目来进行判断即可
- 二分一定可以二分出来边界
- 根据题目+二分出来的边界判断是否有解