1 适用范围
在一个区间上,通过一个性质,可以将区间严格地区分为matched(匹配)和unmatched(不匹配)两部分(这是我自己定义的,方便下面叙述),现在的目的是寻找两部分的分界点(matched区间上靠近unmatched区间的端点)。
(我自己用ps画的图,图丑请见谅 (/⊙ω⊙)/)
2 模板
模板一:适用于matched区间在左侧的情况。
int q[N];
int l = 0 , r = n - 1;
while( l < r )
{
int mid = ( l + r + 1 ) >> 1;
if( check(q[mid]) ) l = mid;
else r = mid - 1;
}
returen l;//此时l==r
模板二:适用于matched区间在右侧的情况。
int q[N];
int l = 0 , r = n - 1;
while( l < r )
{
int mid = ( l + r ) >> 1;
if( check(q[mid]) ) l = mid;
else l = mid + 1;
}
returen l;//此时l==r
3 边界问题
- 为什么模板1是 mid = ( l + r + 1 ) >> 1 而模板二是 mid = ( l + r ) >> 1 ?
当二分的区间较大时,mid取哪一个均不影响二分,真正使二分结果产生偏差的是在最后一步。不难发现,此二分方法的最后一步的步长始终为1,即l和r相邻,因此可以分为两种情况,l在matched上和r在matched上,如图:
现在我们的目的是寻找到matched区间在不同侧条件下,能同时满足 case1 和 case2 的l和r指针的移动方法。
模板一:matched区间在左侧
若当前mid在 check( q[mid] ) 后为ture,则移动左指针( l = mid ), 对应于 case1 , 移动后 l == r == mid == k 。考虑到c++是向下取整,不难发现,mid == ( k - 1 ) + ( k ) + 1 >> 1 == l + r + 1 >> 1;
若当前check(q[mid])为false时,移动右指针 r( r = mid -1 ), 对应于case2 , 移动后 r == l == mid -1 == k,即 mid == k + 1 == ( k ) + ( k + 1 ) + 1 >> 1 == l + r + 1 >> 1; 综上,不难发现mid的表达式应该为:
mid == l + r + 1 >> 1;
模板二:matched区间在右侧
若当前mid在check( q[mid] )后为true,则右指针r向左移动( r == mid ),对应case2,移动后应该为: r == l == mid == k,不难发现 mid == ( k ) + ( k+1 ) >> 1 == l + r >> 1;
若当前mid在check( q(mid) )后为false,则左指针向右移动( l = mid +1 ),对应case1,移动后应该为 l == r == mid +1 == k,即 mid == k - 1 == ( k - 1 ) + ( k ) >> 1 == l + r >> 1; 综上,不难发现mid的表达式应该为:
mid == l + r >> 1;
4 一般解题思路
graph TD
id1[设计check函数]
id2[识别模板]
id3[编写代码]
id1 --> id2 --> id3
- 设计check()函数时要注意:最后返回端点处经check()后一定要为ture
- 识别模板:根据check()函数的逻辑判断matched区间在哪一册,然后对应到模板。或者是思考check()为true后,指针如何变化,然后对应模板
5 例子:数的范围
- 题目
- 思路:首先二分寻找左端点,左端点满足的性质是大于等于x(x是询问的数),因此check()函数就可以直接写为 q[mid] >= x 。有人会问,为什么左端点同样满足小于等于x,为什么check()函数不能写为 q[mid] <= x 呢?因为左端点右侧如果也有与左端点值相等的数,那么该性质不能从左端点处将区间分开。因此, 前面第一步的分析思路有问题。事实上,我们寻找的不是端点处满足的性质,而是从端点处起一侧满足而另一侧不满足的性质。按照这种思路来看,右端点及右端点左侧满足的条件是 小于等于x, 那么check()函数可以写为 q[mid] <= x。解决了check()函数接下来就是识别模板类型了,这一步比较简单,就不多赘述了,下面附上 吸佳佳 代码 (^^)/。
- 吸佳佳代码
#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 -- )
{
int x;
scanf("%d", &x);
int l = 0, r = n - 1;
while (l < r)
{
int mid = l + r >> 1;
if (q[mid] >= x) r = mid;
else l = mid + 1;
}
if (q[l] != x) cout << "-1 -1" << endl;
else
{
cout << l << ' ';
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;
}
}
return 0;
}