ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

二分+三分专题与边界处理

2022-02-21 20:33:21  阅读:183  来源: 互联网

标签:二分 专题 mid bound while 区间 写法 三分


由于本人经常被二分与三分的各种细节卡死,所以记录一下。 二分如果出错了,注意查看二分的初始范围、二分的判断条件、L/R的赋值、最终二分得到的结果等。有时候二分错误并不是因为二分细节问题,而是题目存在边界,需要特判。

 

二分:

目前常用的二分有3种写法,区别在于:①闭区间或左闭右开区间  ②退出条件时l==r?或l==r+1

(1)写法一,左闭右开/左开右闭区间,最终l==r 

int l = 1, r = n + 1, mid; // 对应区间 [l, r)
while (l < r) {
  mid = (l + r) >> 1;
  if (OK(mid)) r = mid;  // 如果mid是OK的,区间变为[l,mid]
  else l = mid + 1;  // 否则变为[mid+1,r]
}
// 另一种写法
int l = 0, r = n, mid; // 对应区间 (l, r]
while (l < r) {
  mid = (l + r + 1) >> 1;
  if (OK(mid)) l = mid;
  else r = mid - 1;
}

个人比较习惯使用这种方法。

1. 上面给出两种写法,区别就:\(mid\) 已经是合法的了,但是希望找到 更小/更大 的 \(ans\)。所以在写代码的时候,就可以关心于 \(check \space mid\) 的值是不是合法的,然后选择让他变大还是变小。

2. 还有一个细节就是初始区间。以左闭右开为例,二分可能会枚举到的范围只有:\([l,r-1]\),所以不用担心 \(r\) 合不合法。如果不断变大,最后 \(l\) 和 \(r\) 都变成右边界。   

 

(2)写法二,闭区间,最终l==r+1,但是碍于边界问题正确的答案应该是\( l \) 。

int l = 1, r = n, mid; // 对应区间 [l, r]
while (l <= r) {
  mid = (l + r) >> 1;
  if (a[mid] <= target) r = mid - 1;  // 返回的结果和等号=所处位置有关
  else l = mid + 1;
}

比如说: \( a = {1,3,3,5} \)。

例一:我们模拟在 \( a \) 数组中找 \( 100 \) ,那么肯定返回的是 \( l == 5 , r == 4 \)

  搜索过程: \( l=1,r=4,mid=2 \) -> \( l=3,r=4,mid=3 \) -> \( l=4,r=4,mid=4 \) -> \( l=5,r=4,break \)。

例二: 如果找 \( 0 \)的话,按上面过程,得出的就是 \( l=1,r=0 \) ,如果是 \(lowerbound\) ,那么l=1无疑是对的。

例三:如果我们找的是3呢? 【假设相等时,令r=mid-1】

  搜索过程: \( l=1,r=4,mid=2 \) -> \(l=1,r=1,mid=1\) -> \( l = 2,r=1,break \) 这时的结果仍然是 \(lowerbound\) 的

  但是我们如果 \( a[mid] == target \) 时,让 \( l = mid + 1\) 会怎么样?

  搜索过程:\( l=1,r=4,mid=2 \) -> \( l = 3,r = 4,mid = 3\) -> \( l = 4,r = 4,mid = 4\) -> \( l=4,r=3,break\)

这个时候结果就是 \(upperbound\) 了,所以使用这种方法时,一定要特别注意等号的位置。 

但你时常会见到这样的写法:其中的 \(INIT\_ANS\) 取决于如果搜不到合法值,你必须设定的值。

int l = 1, r = n, mid, ans = INIT_ANS;
while (l <= r) {
  mid = (l + r) / 2;
  if (ok(mid)) ans = mid, l = mid + 1;
  else r = mid - 1; 
}
cout << ans << endl;

 

 

(3)写法三,闭区间,最终 l + 1 == r ,也就是说,区间内剩下两个数就退出了

while(l + 1 < r) {
  mid = (l + r) >> 1;
  if(OK(mid)) r = mid;
  else l = mid;
}

这三种写法各有优略,第一种是 \(st\) l的 \(lower_bound\) 使用的,第二种目前没找到特殊用法,但是第三种能维护的东西是前两者不能维护的。

比如说:有些题目就是维护间隔的信息,那么应该使用第三种写法。因为 \( [ mid-1 , mid, mid+1 ] \) 三个数之间有两个间隔,我们要舍去另一半的时候,我们应该舍去的是间隔,而不是mid。若舍去的是\( [mid-1, mid] \) ,应该保留的是 \( [mid, mid+1] \) 。所以最后区间剩下的是两个数,也就是一个间隔。【这道题就是这么做的:LINK (欧拉序 + 二分) 其它写法也可做】

 

参考资料:知乎-二分查找有几种方法?

比较喜欢文章里面的一句话:二分无非就是寻找到 \(lower\_bound - 1 、lower\_bound 、 upper\_bound - 1 、 upper\_bound\) 四个值中的一个。 

 

 

 

三分:

 三分咕了。没怎么仔细研究过三分。

int l = 1, r = n, lmid, rmid;
while (l <= r) {
  lmid = l + (r - l) / 3;
  rmid = r - (r - l) / 3;
  if (value(lmid) < value(rmid)) l = lmid + 1;
  else r = rmid - 1;
}
return l or r;

 

标签:二分,专题,mid,bound,while,区间,写法,三分
来源: https://www.cnblogs.com/guanjinquan/p/14348447.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有