二分查找:ios
再也不細說,就是在單調的一段區間查找一個數(或比它大(小))的數。算法
在STL中也有lower_bound,upper_bound兩函數可用。函數
二分查找應用普遍,尤爲配合DP中的數據處理上效率極高。優化
二分答案:spa
在求解一個形如:最大(小)值最小(大)的問題。顯然是從可行解中尋找最優解的過程,那麼易知可行解的取值是單調的。code
所以咱們能夠二分範圍內的解,若解可行,嘗試二分更優解,不然二分較差解。這樣以來,當l==r時,咱們就找到了最優解。blog
綜上能夠看出二分答案的過程實際上就是將求解轉化爲了解的斷定(通常能夠O(n)判斷),在思惟難度和算法複雜度上都有很好的優化。排序
那麼通常來講,二分答案有固定套路,只是在解的斷定上有所不一樣:接口
while(l<=r) { int mid=(l+r)>>1; if(check(mid))r=mid-1; else l=mid+1; } cout<<l;//最大值最小
while(l<=r) { int mid=(l+r)>>1; if(check(mid))l=mid+1; else r=mid-1; } cout<<r;//最小值最大
固然了,若是不知道輸出什麼,那就把mid記錄下來,最後知足check(mid)==true的mid必定是最優解。ci
另外的,二分答案能夠同DP,貪心等東西聯繫起來,這裏有幾道簡單的例題:
進擊的奶牛
最基礎的題,咱們只須要二分距離判斷若是奶牛間距離最小爲mid可否放開所有奶牛就好(固然還須要排序牛棚位置):
bool check(int x) { int before=a[1],total=1; for(int i=2;i<=n;i++) { if(a[i]-before>=x) { before=a[i]; total++; } if(total==c)return 1; } return 0; }
時間管理
咱們按照S_i排序,令二分的mid做爲初始時間,而後從1開始模擬,若是從某處時間超過了S_i,捨去,反之,可行。
bool check(int x) { int _time=x; for(int i=1;i<=n;i++) { _time+=e[i].t; if(_time>e[i].s)return false; } return true; }
數列分段
咱們令二分出的數爲x,則須要數列分紅m部分且每部分和不大於x。
考慮這樣一個貪心:
從頭開始累加,直到超過x,則須要多分一部分,當咱們考慮過全部數,共分爲cnt個部分,那麼此解合法當且僅當cnt<=m。正確性顯然。
bool check(int x) { int now=0,cnt=1; for(int i=1;i<=n;i++) { if(a[i]>x)return false; if(now+a[i]>x) { cnt++; now=0; } now+=a[i]; } return cnt<=m; }
一元三次方程求解
咱們觀察一元三次方程的大體圖像:
如圖即是一個標準狀況下的三次函數圖像。當a>0時經過觀察咱們會發現從左到右的趨勢是:升——降——升。而當a<0時狀況相反。
固然了,輸入保證方程有三個解,爲了方便觀察解的分佈,咱們繪製樣例的圖像:
咱們發現三個解分別分佈在其三個單調區間內,所以咱們能夠在每一個單調區間內二分。
接下來一步是如何求 其單調區間,顯然其單調區間爲:[-100,x1],[x1,x2],[x2,100];關鍵就是求函數的兩極值點。
考慮求導(逃
導函數f’(x)=3ax^2+2bx+c,求其兩零點(題目保證瞭解的存在性),由公式法知:
x1=(-2*b+sqrt(4*b*b-12*a*c))/(6*a);
x2=(-2*b-sqrt(4*b*b-12*a*c))/(6*a);
對於a<0的狀況,爲了統一將a變爲-a,其他係數也乘-1,方程-ax^3-bx^2-cx-d=0 仍成立,不影響答案。
對於增區間和減區間分別二分求解便可。
這裏是以前沒有提過的對於實數的二分:
#include<iostream> #include<cstdio> #include<algorithm> #include<cmath> using namespace std; double a,b,c,d; const double eps=0.001; double judge(double x) { return a*x*x*x+b*x*x+c*x+d;//帶入求值 } double find_up(double l,double r) { while(fabs(r-l)>eps)//控制精度 { double mid=(r+l)/2.0; if(judge(mid)<0)l=mid; else r=mid; } return l; } double find_down(double l,double r) { while(fabs(r-l)>eps) { double mid=(r+l)/2.0; if(judge(mid)<0)r=mid; else l=mid; } return r; } int main() { cin>>a>>b>>c>>d; if(a<0) { a=-a; b=-b; c=-c; d=-d; } double x1=(-2*b+sqrt(4*b*b-12*a*c))/(6*a); double x2=(-2*b-sqrt(4*b*b-12*a*c))/(6*a); printf("%.2f ",find_up(-100.0,x2)); printf("%.2f ",find_down(x2,x1)); printf("%.2f",find_up(x1,100.0)); }
U盤
接口大小直接限制了可選文件的範圍,若是接口大小肯定,那麼可用文件也就肯定了。
咱們二分接口大小,考慮如何check.
咱們對於可用文件跑一個01揹包,那麼f[s]表示大小爲s可獲的最大價值。方案可行當且僅當f[s]>=p;
bool check(int x) { memset(f,0,sizeof(f)); for(int i=1;i<=n;i++) if(w[i]<=x) for(int j=s;j>=w[i];j--) f[j]=max(f[j],f[j-w[i]]+v[i]); return f[s]>=p; }
以上是最基礎的二分答案例題,想深刻理解還要多訓練。
三分:
三分法,適用於求解凸性函數的極值問題,
流程:設當前求解區間爲[l,r],令m1=l+(r-l)/3,m2=r-(r-l)/3;咱們記函數值更優的點爲好點,函數值更劣的點爲壞點。那麼顯然最優勢和好點會與壞點同側,咱們的求解區間就成了[l,m2],
注:單峯函數必須嚴格單調,不然f(m1)==f(m2),將沒法斷定如何縮小左右界
(以上摘自《信息學奧賽一本通·提升篇》 注:良心推薦這本書,你值得擁有!雖然本人在書中找出了幾處小錯誤(手動滑稽),但白璧微瑕,畢竟仍是初版嘛,以後確定會完善的)
固然了對於二分和三分而言,若是你不想卡精度,那麼能夠限制二分次數,這裏再也不贅述。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> using namespace std; const double eps=1e-6; double n,l,r; double a[14]; double f(double x) { double ans=0; for(int i=1;i<=n+1;i++) ans+=a[i]*pow(x,n-i+1); return ans; } int main() { cin>>n>>l>>r; for(int i=1;i<=n+1;i++) cin>>a[i]; while(fabs(r-l)>=eps) { double m1=l+(r-l)/3,m2=r-(r-l)/3; if(f(m1)<f(m2))l=m1; else r=m2; } printf("%.5f",r); }