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

由于本人经常被二分与三分的各种细节卡死,所以记录一下。 二分如果出错了,注意查看二分的初始范围、二分的判断条件、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 mid 的值是不是合法的,然后选择让他变大还是变小。

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

 

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

// 目标:找到等于target的位置
int l = 1, r = n, mid; // 对应区间 [l, r]

// 这种写法求出最左边的合法的位置
while (l <= r) {
  mid = (l + r) >> 1;
  if (a[mid] < target) l = mid + 1;   // a[mid]<target不合法,所以l=mid+1
  else r = mid - 1;                   // 否则a[mid]>=target,r=mid-1
}                                     // 最后最左边的合法位置是 l

// 这种写法求出最右边的合法的位置
while (l <= r) {
  mid = (l + r) >> 1;
  if (a[mid] > target) r = mid - 1;   // a[mid]>target不合法,所以r=mid-1
  else l = mid + 1;                   // 否则a[mid]<=target,l=mid+1
}                                     // 最后最右边的合法位置是 r

比如说: 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;
}

这三种写法各有优略,第一种是 stllowerbound 使用的,第二种是一种比较稳妥的方法,但是第三种能维护的东西是前两者不能维护的。

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

 

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

比较喜欢文章里面的一句话:二分无非就是寻找到 lower_bound1lower_boundupper_bound1upper_bound 四个值中的一个。 

 

 

实数二分

如果设置精度eps=1e-9,然后最大值达到1e9的话,double/longdouble都是无法精确表示1e18的(这一点可以根据IEEE浮点数了解一下)。

正是因为精度太低的时候无法精确表示,有时候会被卡死循环。【虽然我的写法没被卡过。。。】

while (l + eps < r) {
    mid = (l + r) / 2.0;
    if (ok(mid)) l = mid;
    else r = mid - eps;
} // 我的奇怪写法

所以二分的时候不应该卡精度,而应该卡次数进行二分。

关于这个二分的次数怎么选择:

(1)看看整数部分最大会出现到多大,如果是1e18,整数部分就需要64次左右的二分的

(2)再看看小数点后面需要多少次,如果精度是1e-9,那么至少要 32次。

总的来说,100次完全足够了,如果题目卡时间,可以进一步减少二分次数。

// 精度为 1e-8
  auto ok = [&](double mid) { return mid <= 1 + 1e-8; };
  double l = 0.0, r = 1e9, mid;
  for (int i = 1; i <= 100; i++) {
    mid = (l + r) / 2.0;
    if (ok(mid)) l = mid;
    else r = mid;
  }
  // 输出 1.00000001 1 + 1e-8
  cout << fixed << setprecision(8) << l << endl;

 

三分:注意三分一定是严格单调的,不能有相等的情况。

 这个三分的常数有点高的,如果题目卡常,有可能会G。

常见问题:

(1)三分死循环 - 记得 l=lmid+1,r=rmid-1,因为(r-l)<3的时候,lmid和rmid都等于L、R,如果不加减1,就会G掉。

(2)三分得到的L、R到底哪个才是答案 - 下面的板子得到的L==R

(3)三分得到的L、R到底是峰值的最左侧还是峰值的最右侧 - 这个需要根据if-else的等号进行分析 

int l = 1, r = n, lmid, rmid, ans = INF;
while (l < r) {  // 这里可以不加等号吗?(有些题目不加等号可以通过,但是具体不清楚)
  lmid = l + (r - l) / 3;
  rmid = r - (r - l) / 3;
  // 这里加等号,三分完之后的L就是峰值的最右侧的位置,如果不加,就是最左侧的位置
  if (value(lmid) <= value(rmid)) l = lmid + 1, ans = min(ans, value(lmid));
  else r = rmid - 1, ans = min(ans, value(rmid));
}
return l or r;  // 如果while循环里面不加等号,最后一定会有l==r

 

如果令 L<=R 为循环条件,返回 L 一般都是正确的。

 

posted @   PigeonG  阅读(90)  评论(0编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示