zoukankan      html  css  js  c++  java
  • [一本通学习笔记] 二分与三分

    摘要

    本节主要关于二分与三分的问题,二分与三分时我们通常会区分整数和浮点而做不同的处理。

    整数二分

    最经典的一类,我们通常这样写

    int L=... , R = ...;
    while(R>L) {
        ...
        if(...) L=mid+1;
        else R=mid;
    }
    cout<<L (-1) <<endl;

    需要注意的是在这里最终答案是否-1取决于L=mid+1这一切换进行时,mid是否为合法解。如果是,那么-1. 如果不是,就输出L。

    当然还存在一些别的写法,比如将判断条件改为R>=L,切换语句写作L=mid+1,r=mid-1,同时记录最后一次合法的Ans。弄清楚一种即可。

    浮点数二分

    浮点类二分非常容易,只需要注意控制精度误差即可。

    浮点数三分


    三分法通常用于找凸函数极值(虽然理论上对导函数二分也挺好),这时候需要用到两个mid值,即m1和m2,分别对应区间[l,r]的两个三等分点,计算出两个三等分点,然后根据需要将左端点缩到左三等分点,或者右端点缩到右三等分点,这样来进行转移即可。在浮点数情况下实现是很容易的。

    整数三分

    有时候我们需要对整数三分,就需要注意一下取整误差的问题,提供参考写法如下。

    int l=..., r=...;
    while(r>l){
        int m1=(2*l+r)/3, m2=(2*r+l+2)/3;
        if(...) r=m2-1;
        else l=m1+1;
    }
    cout<<l<<endl;

    二分答案+验证

    如果问题的答案与决定性参变量的函数关系是非严格单调的,我们就可以用二分来将它转化为若干判定性问题,这在处理很多问题时是非常有效的。

    二分套二分/三分套三分

    当决定性参变量有多个,并且函数对每个参变量的偏导数都是恒非正或恒非负的时候,我们既可以用二分套二分来解决问题。即对f(x,y),我们先二分一个x,将它转化为f(x,y)=g(y),然后求出g(y)的答案,就得到了f(x,y)在这个x上的答案。

    LOJ10011. 「一本通 1.2 例 1」愤怒的牛

    #include <bits/stdc++.h>
    using namespace std;
    int n,m,a[1000005];
    int main() {
        cin>>n>>m;
        for(int i=1;i<=n;i++) cin>>a[i];
        sort(a+1,a+n+1);
        int l=0,r=1e+9;
        while(r>l) {
            int mid = (l+r)/2;
            int las = -1e+9, cnt = 0;
            for(int i=1;i<=n;i++)
                if(a[i]-las >= mid)
                    ++cnt, las = a[i];
            if(cnt>=m) l=mid+1;
            else r=mid;
        }
        cout<<l-1<<endl;
    }

    LOJ10012. 「一本通 1.2 例 2」Best Cow Fences

    这里涉及到二分答案的一个常用技巧,即对分数形式,比如这里的ans = (sum(...))/(sum(1)),我们二分一个ans,那么不等式ans > (sum(...))/(sum(1))就可以转化为sum(a[i]-ans)>0,通过后缀最大值处理一下就可以了。

    浮点数问题一定要注意补偿舍入误差。

    #include <bits/stdc++.h>
    using namespace std;
    
    int n,l,a[1000005];
    double s[1000005];
    
    int main() {
        cin>>n>>l;
        for(int i=1;i<=n;i++) cin>>a[i];
        double L=0, R = 1e+9;
        while(R-L > 1e-6) {
            double mid = (L+R) / 2.0;
            for(int i=1;i<=n;i++)
                s[i] = s[i-1] + a[i] - mid;
            int flag = 0;
            s[n+1] = -1e+19;
            for(int i=n-l;i>=0;--i)
                s[i+l] = max(s[i+l], s[i+l+1]),
                flag |= (s[i+l]-s[i]>-1e-6);
            if(flag) L=mid;
            else R=mid;
        }
        cout<<(int)(L*1000.0+0.005)<<endl;
    }

    #10013. 「一本通 1.2 例 3」曲线

    #include <bits/stdc++.h>
    using namespace std;
    
    int T,n;
    double a[10005],b[10005],c[10005],t1,t2,t3;
    
    double f(double x) {
        double ret = -1e+18;
        for(int i=1;i<=n;i++)
            ret = max(ret, a[i]*x*x+b[i]*x+c[i]);
        return ret;
    }
    
    int main() {
        ios::sync_with_stdio(false);
        cin>>T;
        while(T--) {
            cin>>n;
            double ans = -1e+18;
            for(int i=1;i<=n;i++)
                cin>>a[i]>>b[i]>>c[i];
            double l=0,r=1000;
            while(r-l > 1e-11) {
                double m1 = l + (r-l)/3.0, m2 = r - (r-l)/3.0;
                if(f(m1)>f(m2)) l=m1;
                else r=m2;
            }
    
            cout<<setiosflags(ios::fixed)<<setprecision(4)<<f(l)<<endl;
        }
    }

    #10014. 「一本通 1.2 练习 1」数列分段 II

    #include <bits/stdc++.h>
    using namespace std;
    
    int n,m,a[1000005];
    
    int main() {
        cin>>n>>m;
        int mx=0;
        for(int i=1;i<=n;i++) cin>>a[i], mx=max(mx,a[i]);
        int l=mx,r=1e+9;
        while(r-l>0) {
            int mid = (l+r)/2;
            int sum = 0, cnt = 1;
            for(int i=1;i<=n;i++) {
                if(sum+a[i]>mid) ++cnt,sum=0;
                sum+=a[i];
            }
            if(cnt<=m) r=mid;
            else l=mid+1;
        }
        cout<<l<<endl;
    }

    #10015. 「一本通 1.2 练习 2」扩散

    二分答案后考虑如何检验,暴力枚举点对看是否直接相连,并查集维护连通性即可。

    #include <bits/stdc++.h>
    using namespace std;
    
    int n,x[100005],y[100005],f[105];
    
    int find(int x) {
        return (f[x]==x)?x:(f[x]=find(f[x]));
    }
    
    int dis(int x1,int y1,int x2,int y2) {
        return abs(x1-x2) + abs(y1-y2);
    }
    
    int main() {
        cin>>n;
        for(int i=1;i<=n;i++) cin>>x[i]>>y[i];
        int l=0,r=1e+9;
        while(r-l) {
            int mid = (l+r)/2, cnt = n;
            for(int i=1;i<=n;i++) f[i]=i;
            for(int i=1;i<=n;i++)
                for(int j=1;j<i;j++)
                    if(dis(x[i],y[i],x[j],y[j])<=2*mid)
                        if(find(i)!=find(j)) f[find(i)]=find(j), --cnt;
            if(cnt==1) r=mid;
            else l=mid+1;
        }
        cout<<l<<endl;
    }

    #10016. 「一本通 1.2 练习 3」灯泡

    几何推公式

    #include <bits/stdc++.h>
    using namespace std;
    
    int T; double H,h,D;
    
    double f(double x) {
        double ret = h/(H-h)*x;
        if(ret+x > D)
            ret = D - x + (ret+x - D) * (H-h) / x;
        return ret;
    }
    
    int main() {
        cin>>T;
        while(T--) {
            cin>>H>>h>>D;
            double L=0,R=D;
            while(R-L > 1e-8) {
                double m1 = L + (R-L)/3, m2 = R - (R-L)/3;
                if(f(m1)>f(m2)) R=m2;
                else L=m1;
            }
            cout<<setiosflags(ios::fixed)<<setprecision(3)<<f(L)<<endl;
        }
    }

    #10017. 「一本通 1.2 练习 4」传送带

    三分套三分,设两个比例k,u。

    #include <bits/stdc++.h>
    using namespace std;
    
    double ax,ay,bx,by,cx,cy,dx,dy,p,q,r;
    
    double f(double x1,double y1,double x2,double y2) {
        return sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
    }
    
    double calc(double k,double u) {
        double x1=k*ax+(1-k)*bx, y1=k*ay+(1-k)*by;
        double x2=u*cx+(1-u)*dx, y2=u*cy+(1-u)*dy;
        return f(ax,ay,x1,y1)/p+f(x1,y1,x2,y2)/r+f(x2,y2,dx,dy)/q;
    }
    
    double inner(double m) {
        double L=0,R=1;
        while(R-L > 1e-8) {
            double m1=L+(R-L)/3, m2=R-(R-L)/3;
            if(calc(m,m1)<calc(m,m2)) R=m2;
            else L=m1;
        }
        return calc(m,L);
    }
    
    int main() {
        cin>>ax>>ay>>bx>>by>>cx>>cy>>dx>>dy>>p>>q>>r;
        double L=0,R=1;
        while(R-L > 1e-8) {
            double m1=L+(R-L)/3, m2=R-(R-L)/3;
            if(inner(m1)<inner(m2)) R=m2;
            else L=m1;
        }
        cout<<setiosflags(ios::fixed)<<setprecision(2)<<inner(L)<<endl;
    }

    #include <bits/stdc++.h>using namespace std;
    double ax,ay,bx,by,cx,cy,dx,dy,p,q,r;
    double f(double x1,double y1,double x2,double y2) {    return sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));}
    double calc(double k,double u) {    double x1=k*ax+(1-k)*bx, y1=k*ay+(1-k)*by;    double x2=u*cx+(1-u)*dx, y2=u*cy+(1-u)*dy;    return f(ax,ay,x1,y1)/p+f(x1,y1,x2,y2)/r+f(x2,y2,dx,dy)/q;}
    double inner(double m) {    double L=0,R=1;    while(R-L > 1e-8) {        double m1=L+(R-L)/3, m2=R-(R-L)/3;        if(calc(m,m1)<calc(m,m2)) R=m2;        else L=m1;    }    return calc(m,L);}
    int main() {    cin>>ax>>ay>>bx>>by>>cx>>cy>>dx>>dy>>p>>q>>r;    double L=0,R=1;    while(R-L > 1e-8) {        double m1=L+(R-L)/3, m2=R-(R-L)/3;        if(inner(m1)<inner(m2)) R=m2;        else L=m1;    }    cout<<setiosflags(ios::fixed)<<setprecision(2)<<inner(L)<<endl;}

  • 相关阅读:
    51NOD 1069 Nim游戏
    51NOD 1066 Bash游戏
    51NOD 1058 N的阶乘的长度
    51NOD 1057 N的阶乘
    51NOD 1027 大数乘法
    RMQ 区间最大值 最小值查询
    Codeforces Round #426 (Div. 2) C. The Meaningless Game
    51NOD 1046 A^B Mod C
    OJ上 编译器 G++和C++的区别
    二分暑假专题 训练记录 2017-7-29
  • 原文地址:https://www.cnblogs.com/mollnn/p/11583991.html
Copyright © 2011-2022 走看看