首先......我是一個很菜很菜的萌新,所以這篇文章寫得很詳細,有很多我自己的口水話方便我理解,請各位謹慎食用qwq
以前在網上找過很多介紹二分的博客,但都感覺對萌新不太友好,反正我當時連跳石頭都沒看懂,所以決定自己寫一篇!其中有我的想法,也借鑒了書里的很多內容,感謝lyd。
二分答案,顧名思義,就是對我們所需要的答案進行二分,對我們要求的值進行二分。
二分的基礎用法是在單調序列或者單調函數當中查找,當答案具有單調性,我們就可以采用二分來計算,當然還有三分,在后面我會詳細講到
整數集合上的二分
在單調遞增序列a中查找>=x的數當中最小的一個
while(l<r) { int mid=(l+r)>>1; if(a[mid]>=x) r=mid; //普適模板:if(check()) r=mid;else l=mid+1;,下同。 else l=mid+1; } return a[l];
在單調遞增序列a中查找<=x的數當中最大的一個
while(l<r) { int mid=(l+r+1)>>1; if(a[mid]<=x) l=mid; else r=mid-1; } return a[l];
根據以上兩種代碼形式 可以總結出二分的兩種常用形式
1.r=mid,l=mid+1,取中間值的時候,mid=(l+r)>>1;
2.l=mid,r=mid-1,取中間值的時候,mid=(l+r+1)>>1;
注意
1.如果不對mid的取法進行區分,可能會造成出錯的情況,在平時的練習當中,希望大家能注意選擇,我不再贅述,《進階指南》里面講得很詳細啦。
2.在二分當中,我們采用的是右移運算而不是除法,是因為右移運算是向下取整的,但除法向0取整。
終止條件:l==r 也就是答案所在的位置啦
實數域上的二分
實數域會方便很多,具體兩種方法。
1.給出精度eps 以l+eps<r為條件
while(l+eps<r)
{
//自己寫啦!
}
2.暴力循環100次 完全不用擔心不夠 2^100已經超過了int類型~
for(int i=0;i<100;i++)
{
//自己寫.jpg
}
關於二分的知識點大概就是上面這些,但是大家在寫代碼的時候一定要注意各種邊界條件,比如說l和r之間可不可以取等號,又或者mid是+1,-1還是不加不減。
我個人感覺二分當中的坑點很多,反正我經常因為各種亂七八糟的邊界條件debug老半天,肯定是編譯器的鍋!可能我還是比較菜對題目/模板的理解不夠深......
下面給出兩道練習題
洛谷P1873砍樹 題解(附帶原OJ鏈接)
洛谷P2678跳石頭(NOIP2015) 題解(附帶原OJ鏈接)
關於三分
三分,顧名思義就是分成三份,它主要用於求單峰函數的極值。
單峰函數,就是一個有極大值或者極小值的函數。
如果有極大值,那么極大值的左邊是單調遞增,右邊單調遞減;
如果有極小值,那么極小值的左邊是單調遞減,右邊是單調遞增。
其實我腦袋里面已經自動腦補了一個二次函數了......
(但兩者不能划等號哦!二次函數是單峰函數,但單峰函數中僅僅是包含了二次函數)
關於單峰函數求極值的分析
以一個有極大值的單峰函數為例(如圖所示)
若此函數有縱坐標y1<y2,那么它橫坐標的情況就是兩種
1.x1和x2都在極大值的左邊,並且x1<x2
2.x1在極大值的左邊,x2在極大值的右邊
通過分析,我們可以發現,x1總在極大值的左邊。
若此函數有縱坐標y1>y2,那么它橫坐標的情況也是兩種。
1.x1和x2都在極大值的右邊,並且x1<x2(感謝我の學長兼男神cjrhahahahaha指正此處 應為x1<x2 ovo)
2.x1在極大值的左邊,x2在極大值的右邊
通過分析,我們可以發現,x2總在極大值的右邊。
根據以上兩種操作,我們可以不停進行三分,直到找到極大值,對於有極小值的單峰函數也是同一個道理。
放兩張圖在這里,大家自行理解吧......
針對有極大值,且有y1<y2的單峰函數
針對有極小值,且有y1>y2的單峰函數。
qwq三分的知識其實比起二分更簡單易懂,因為它能解決的題目范圍更狹窄,但需要較強的數學思維,在題目中
能夠找出單峰函數的思想。 二分只是一個簡單的模板,如果運用到具體的問題當中,要先理清楚這道題有沒有二分思想,
同時呢,我認為二分當中的核心應該是check()函數。
下面給出一道題
洛谷P1873 傳送帶(SCOI2010) 題解(附原OJ鏈接)
二分的題解就到這里了,很感慨。第一次接觸二分我還是一個普及組萌新時根本就看不懂跳石頭,甚至完全無法理解......
事實證明,成長的道路上荊棘滿布,但只要我們勇敢前行,就沒有無法克服的艱難險阻!