在你真的理解二分的写法吗 - 二分写法详解这篇博客中,有几个朋友给二分求中点mid = (l + r) / 2提出了一些疑问和改进。自己最近也对这个问题有过一些思考,因此在这里系统详细地聊聊自己的看法,看看程序中求区间终点到底应该怎么写才是完美的。

先公布程序中求区间中点的完美写法:mid = l + (r - l) / 2

在数学上,mid = l + (r - l) / 2mid = (l + r) / 2是等价的,这个自己通分一下就行了。但是在程序中,往往需要求一个数组区间的中点,这也就意味着区间必须是一个整数,如果中点是x.5这种情况,你是向下取整还是向上取整。这在编程中是一个很大的坑。

mid = (l + r) / 2的劣势

首先看看mid = (l + r) / 2这种求中点有什么劣势。

  • 溢出
  • 求上下界会不统一

溢出

溢出比较好理解,l + r可能会溢出int的最大范围,而l + (r - l) / 2不会,这里用减法替代了加法,这种思想很多地方都有用到,比如求最小一个数,这个数的平方大于或等于给定的一个值n,直观代码的写法如下:

int x;
for (x = 0; x * x < n; ++x) {}

循环跳出,x就是答案,但是如果n = (1 << 31) - 1呢?这个循环永远不会结束,可以试试看,原因就是x * x会大过int的最大值,从而导致溢出出现负数。正确的写法如下:

int x;
for (x = 0; x < n * 1.0 / x; ++x) {}

这就是利用除法代替乘法,规避了溢出的分险,从数学的角度来看,这两者等价,但是在编程的领域中差别很大。下面分享一个题,你就更能体会这种思想了。

这是2018湘潭邀请赛的F题,题目很好理解,给三元组排序,排序规则就是题中的不等式。这题一看不就很好写嘛,sort一下,cmp函数自己写一下,但是先看看数据范围,1≤ai,bi,ci≤1091≤ai,bi,ci≤1091 \le a_i, b_i, c_i \le 10^9。对于这种分式不等式,最好的做法就是把分式转化成乘法,也就是2A3B≤2A′3B′→6AB′≤6A′B2A3B≤2A′3B′→6AB′≤6A′B\frac{2A} {3B} \le \frac{2A'} { 3B'} \rightarrow 6AB' \le 6A'B,把数据范围代入进去,发现2.4×10192.4×10192.4\times10^{19},而<stdint.h>unsigned long long的最大值:#define UINT64_MAX 0xffffffffffffffffULL /* 18446744073709551615ULL */,显然直接做数据范围承受不了。

这个题正确的做法应该是把2A3B≤2A′3B′2A3B≤2A′3B′\frac{2A} {3B} \le \frac{2A'} { 3B'}转化成3B−2A3B≥3B′−2A′3B′3B−2A3B≥3B′−2A′3B′\frac{3B - 2A} {3B} \ge \frac{3B'-2A'} { 3B'},这样子(3B−2A)∗3B′(3B−2A)∗3B′(3B-2A)*3B'的最大值为1.2×10191.2×10191.2\times 10^{19},这是可以承受的。

通过代码如下:

#include <bits/stdc++.h>using namespace std;typedef unsigned long long uLL;typedef tuple<uLL, uLL, uLL, int> Node;int main()
{for (size_t n; EOF != scanf("%u", &n); ) {vector<Node> arr;uLL x, y, z;for (size_t i = 0; i < n; ++i)scanf("%lld%lld%lld", &x, &y, &z),arr.emplace_back(x, y, z, i);sort(arr.begin(), arr.end(), [] (const Node &A, const Node &B){uLL AA = get<2>(A) * (get<0>(B) + get<1>(B) + get<2>(B));uLL BB = get<2>(B) * (get<0>(A) + get<1>(A) + get<2>(A));return AA == BB ? get<3>(A) < get<3>(B) : AA > BB;});for (size_t i = 0; i < n; i++)printf("%u%c", get<3>(arr[i]) + 1, i == n - 1 ? '\n' : ' ');}return 0;
}

避免数据溢出的技巧还有很多,这里就先介绍到这里。

求上下界不统一

这个很有趣,一般不怎么会注意求上下界不统一这个坑,可以看下这篇博客右移一位和除二的区别。举个例子,区间[2, 5]的中点求下界是mid = (2 + 5) / 2 = 3,这没问题;区间[-5, 2]的中点求下界是mid = (-5 + 2) / 2 = -1,这里就出现问题了,[-5, 2]求下界是-2。而(l + r) / 2求出来是-1,也就是说(l + r) / 2想要正确求出区间中点的上下界就要针对(l + r)的正负做不同的处理。而用mid = l + (r - l) / 2就不会出现上述问题。

mid = l + (r - l) / 2的优势

mid = (l + r) / 2的劣势其实就是mid = l + (r - l) / 2的优势,这里总结一下:

  • 不会溢出
  • 上下界求法统一
    下界:mid = l + (r - l) / 2
    上界:mid = l + (r - l + 1) / 2

为什么很多人还是会写mid = (l + r) / 2

这是个问题,因为我也这样写,我总结了三点原因,如下。

  • 代码短,简单
  • 溢出这个问题一般不会发生,因为如果左右端点的范围到了10910910^9,我都会选用long long,因此就不会发生溢出
  • 由于二分是求一个数组或向量的区间中点,那么l + r肯定不会是负数,那么也不会出现上下界不统一的问题

这也是为什么mid = (l + r) / 2能活这么久,且很多人没有发现mid = l + (r - l) / 2好处的原因。

总结

一个很小的细节里面的思想很精髓,我们应该多去思考“为什么是这样,为什么不是那样,那样和这样的区别是什么,优劣势是什么”。

C++标准库中STL的lower_boundupper_bound也是时候解开它们的神秘面纱了,近期会写,只能说标准库中到处都是精髓,且标准库中的上下界二分比你真的理解二分的写法吗 - 二分写法详解这篇博客中的二分更好理解。

二分以及编程过程中求中点各种写法思想解析以及完美写法相关推荐

  1. UG编程过程中的几个小技巧

    UG编程过程中的几个小技巧 [一.UG运用小技巧] 当平移工作坐标系时经过向下按住ALT键,能够履行准确定位. 制图中,能够在线性尺度上用SHIFT-拖动来创立狭窄型尺度. 能够双击在资源条中的导航器 ...

  2. c语言错误出在花括号上,初学者在学习c语言编程过程中常出现的错误分析

    在高校经常中开设的第一科关于程序设计的语言就是C语言,这种语言有很多优势,比如说使用比较灵活,功能比较强大,可以用于编写程序.软件,但是对于初学者来说, 1 0 8 应用方法论 22第霸 0赫 6乱 ...

  3. python之禅 中文_《Python之禅》中对于Python编程过程中的一些建议

    <Python之禅>中对于Python编程过程中的一些建议 来源:中文源码网    浏览: 次    日期:2018年9月2日 [下载文档:  <Python之禅>中对于Pyt ...

  4. 实例——在编程过程中进行单元测试

    这是一个真枪实弹的例子,如何在编程过程中进行单元测试. 本文以一个图书馆的书籍管理系统作为例子,讲述一个单元测试--编码的循环过程.这里只讲述一个独立画面的编写.画面的编码分两个部分,业务数据部分和界 ...

  5. 学习编程过程中-->遇到的错误

    文章目录 学习编程过程中-->遇到的错误(吕招焕) 1.java.sql.SQLException: Parameter index out of range (2 > number of ...

  6. Qt的日常编程过程中遇见的问题和使用

    Qt的日常编程过程中遇见的问题和注意 Qt的日常编程过程中遇见的问题 1.QString和String的转化的格式问题,中文转化过程中会出现问题. 2.使用qcustomplot的时候出现错误:LIN ...

  7. AOP编程过程中的JoinPoint接口

    在AOP编程过程中需要使用到的一个 JoinPoint 接口,用来获取调用的信息,下面是这个接口的代码 // // Source code recreated from a .class file b ...

  8. JAVA面试要点004_JAVA编程过程中为了性能优化_应该注意到的地方

    最近的机器内存又爆满了,出了新增机器内存外,还应该好好review一下我们的代码,有很多代码编写过于随意化,这些不好的习惯或对程序语言的不了解是应该好好打压打压了. 下面是参考网络资源和总结一些在ja ...

  9. 编程中python怎么读-对Python新手编程过程中如何规避一些常见问题的建议

    这篇文章收集了我在Python新手开发者写的代码中所见到的不规范但偶尔又很微妙的问题.本文的目的是为了帮助那些新手开发者渡过写出丑陋的Python代码的阶段.为了照顾目标读者,本文做了一些简化(例如: ...

  10. 转 PHP编程过程中需要了解的this,self,parent的区别

    {一}PHP中this,self,parent的区别之一this篇 面向对象编程(OOP,Object Oriented Programming)现已经成为编程人员的一项基本技能.利用OOP的思想进行 ...

最新文章

  1. python编程入门经典实例-Python入门经典实例
  2. TCP报文段首部格式详解
  3. SqlServerException:拒绝对表对象的select,insert权限解决(新建账号导致的问题)
  4. maven 文件上传下载_使用Maven将文件上传和下载到S3
  5. 伸缩轨道_深度解析——伸缩喷漆房为什么这么受欢迎!
  6. 车仪表台上的装饰_一汽大众销量最差的车,旅行车蔚领为什么在国内没有市场?...
  7. Linux下批量修改文件名
  8. CSS学习笔记--CSS语法与选择器
  9. World Currency Symbols世界货币符号
  10. jax-ws 实现webservice
  11. 可以势利,不可以小人
  12. 鸿蒙2.0手机交互体验,鸿蒙 2.0手机应用开发者Beta,来了!全新交互体验
  13. 【MySQL】为什么要学习MySQL数据库呢?
  14. Muzli – 所有你需要的设计灵感都在这
  15. 正面管教读书笔记 02 几个基本概念
  16. 二期:Combined Scorecards
  17. 梯度下降-5463. 服务中心的最佳位置
  18. 某选秀比赛的晋级规则是:如果7个评委中,有4个及以上评委投赞成票。试用数组编写程序判断某选手是否晋级
  19. 《生活,是很好玩的》读书日记
  20. window自带的桌面整理工具

热门文章

  1. matlab单字音频合成,基于MATLAB的音频解析与合成
  2. 网络嗅探器(影音神探) v4.63 绿色正式版http://down.hotlife.cn/html/download/2006/6/05/1149478572.shtml
  3. gst-launch-1.0用法小记
  4. BIOS的全局变量gST gBS gDS
  5. java 免费 cms建站系统_最受欢迎免费开源CMS建站系统排行榜
  6. Gauntlet风险评级上线: DeFi杠杆率多高?会不会崩盘?
  7. PIL Image P和L模式
  8. Android 11.0 12.0强制app横屏显示
  9. Jmeter 线程数、Ramp-Up、循环次数 详解
  10. Android CoordinatorLayout自定义Behavior实现依赖滚动布局