关于二分与三分(基础)

二分查找: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); }
相关文章
相关标签/搜索