二分以及编程过程中求中点各种写法思想解析以及完美写法
在你真的理解二分的寫法嗎 - 二分寫法詳解這篇博客中,有幾個朋友給二分求中點mid = (l + r) / 2提出了一些疑問和改進。自己最近也對這個問題有過一些思考,因此在這里系統詳細地聊聊自己的看法,看看程序中求區間終點到底應該怎么寫才是完美的。
先公布程序中求區間中點的完美寫法:mid = l + (r - l) / 2。
在數學上,mid = l + (r - l) / 2和mid = (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≤109。對于這種分式不等式,最好的做法就是把分式轉化成乘法,也就是2A3B≤2A′3B′→6AB′≤6A′B2A3B≤2A′3B′→6AB′≤6A′B,把數據范圍代入進去,發現2.4×10192.4×1019,而<stdint.h>中unsigned long long的最大值:#define UINT64_MAX 0xffffffffffffffffULL /* 18446744073709551615ULL */,顯然直接做數據范圍承受不了。
這個題正確的做法應該是把2A3B≤2A′3B′2A3B≤2A′3B′轉化成3B?2A3B≥3B′?2A′3B′3B?2A3B≥3B′?2A′3B′,這樣子(3B?2A)?3B′(3B?2A)?3B′的最大值為1.2×10191.2×1019,這是可以承受的。
通過代碼如下:
#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
這是個問題,因為我也這樣寫,我總結了三點原因,如下。
- 代碼短,簡單
- 溢出這個問題一般不會發生,因為如果左右端點的范圍到了109109,我都會選用long long,因此就不會發生溢出
- 由于二分是求一個數組或向量的區間中點,那么l + r肯定不會是負數,那么也不會出現上下界不統一的問題
這也是為什么mid = (l + r) / 2能活這么久,且很多人沒有發現mid = l + (r - l) / 2好處的原因。
總結
一個很小的細節里面的思想很精髓,我們應該多去思考“為什么是這樣,為什么不是那樣,那樣和這樣的區別是什么,優劣勢是什么”。
C++標準庫中STL的lower_bound和upper_bound也是時候解開它們的神秘面紗了,近期會寫,只能說標準庫中到處都是精髓,且標準庫中的上下界二分比你真的理解二分的寫法嗎 - 二分寫法詳解這篇博客中的二分更好理解。
總結
以上是生活随笔為你收集整理的二分以及编程过程中求中点各种写法思想解析以及完美写法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Win10代理自动打开无法永久关闭的问题
- 下一篇: matlab给定振形用图表示,基于 MA