• 2019年8月训练(壹)二分,三分


    二分查找

    P1024 一元三次方程求解

    题目给出范围[-100,100],同时两根绝对值之差<=1,保证了每一个大小为1的区间里至多有1个解,也就是说当区间的两个端点的函数值异号时区间内一定有一个解,同号时一定没有解。

    也就可以二分去做查找。

    AC码:

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    
    double a,b,c,d;
    
    double f(double x)
    {
        return a*x*x*x+b*x*x+c*x+d;
    }
    
    int main()
    {
        double l,r,x1,x2,mid;
        int cnt=0;
        scanf("%lf%lf%lf%lf",&a,&b,&c,&d);
        for(int i=-100;i<=100;i++)
        {
            l=i;
            r=i+1;
            x1=f(l);
            x2=f(r);
            if(!x1)
            {
                printf("%.2lf ",l);
                cnt++;
            }
            if(x1*x2<0)
            {
                while((r-l)>=0.001)
                {
                    mid=(l+r)/2;
                    if(f(mid)*f(r)<=0)
                    {
                        l=mid;
                    }
                    else r=mid;
                }
                printf("%.2lf ",r);
                cnt++;
            }
            if(cnt==3) break;
        }
        return 0;
    }

    这里对以下的一段代码稍稍说明一下

    while((r-l)>=0.001)//区间左右距离在可行的范围内
                {
                    mid=(l+r)/2;//中间值,也就是二分点
                    if(f(mid)*f(r)<=0)//判断条件(根据题意可以进行替换)
                    {
                        l=mid;//接着查找右区间[mid,r]
                    }
                    else r=mid;//接着查找左区间[l,mid]
                }

    上面就是二分的核心部分,至于判断条件,根据题意自己写个·bool函数吧

    P1182 数列分段 Section II

     这道题难得不是二分,而是判断(每道都是这样)。
    这里提供一种方法

    首先画一条线,也就是分段后和的最大值(一般去mid),也就是这里的now。

    然后逐渐减小这个now,只要分段数(cnt)合法。

    第二次:now=6,cnt=3.

    第三次:now=5,但是cnt=4(不合法),结束。

    AC码:

    #include<cstdio>
    #include<cstring>
    #define maxn 100010
    #include<algorithm>
    using namespace std;
    
    int n,m,a[maxn],now,cnt;
    
    bool pd(int mid)
    {
        now=mid;cnt=1;
        for(int i=1;i<=n;i++)
        {
            if(now<a[i])
            {
                cnt++;
                now=mid-a[i];
            }
            else now-=a[i];
        }
        return cnt<=m;
    }
    
    int main()
    {
        int l=0,r=0;
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&a[i]);
            l=max(l,a[i]);
            r+=a[i];
        }
        int mid;
        while(l<r)
        {
            mid=(l+r)>>1;
            if(pd(mid)) r=mid;
            else l=mid+1;
        }
        printf("%d",l);
        return 0;
    }

    P2678 跳石头

    二分跳跃距离,然后把这个跳跃距离“认为”是最短的跳跃距离,然后去以这个距离为标准移石头。判断这个解是不是可行解。如果这个解是可行解,那么有可能会有比这更优的解,那么我们就去它的右边二分。为什么去右边?答案是,这个区间是递增的 ,而我们求的是最短跳跃距离的最大值,显然再右边的值肯定比左边大,那么我们就有可能找到比这更优的解,直到找不到,那么最后找到的解就有理由认为是区间内最优解。反过来,如果二分到的这个解是一个非法解,我们就不可能再去右边找了。因为性质,右边的解一定全都是非法解。那么我们就应该去左边找解。

    如何实现判断呢?可以去模拟这个跳石头的过程。开始你在i(i=0)位置,我在跳下一步的时候去判断我这个当前跳跃的距离,如果这个跳跃距离比二分出来的mid小,那这就是一个不合法的石头,应该移走。为什么?我们二分的是最短跳跃距离,已经是最短了,如果跳跃距离比最短更短岂不是显然不合法,是这样的吧。移走之后要怎么做?先把计数器加上1,再考虑向前跳啊。去看移走之后的下一块石头,再次判断跳过去的距离,如果这次的跳跃距离比最短的长,那么这样跳是完全可以的,我们就跳过去,继续判断,如果跳过去的距离不合法就再拿走,这样不断进行这个操作,直到i = n+1,为啥是n+1?河中间有n块石头,显然终点在n+1处。(这里千万要注意不要把n认为是终点,实际上从n还要跳一步才能到终点)。模拟完这个过程,我们查看计数器的值,这个值代表的含义是我们以mid作为答案需要移走的石头数量,然后判断这个数量 是不是超了就行。如果超了就返回false,不超就返回true。

    AC码:

    #include<cstdio>
    #include<cstring>
    #define maxn 500010
    #include<algorithm>
    using namespace std;
    
    int l,r,n,m,a[maxn],d,ans;
    
    bool pd(int mid)
    {
        int tot=0,next=0,now=0;//next下个石头、now当前所在石头
        while(next<n+1)
        {
            next++;
            if(a[next]-a[now]<mid)
            {
                tot++;//拿走石头,再看下一块
            }
            else now=next;//不拿,直接跳过去
        }
        if(tot>m) return false;
        else return true;
    }
    
    int main()
    {
        scanf("%d%d%d",&d,&n,&m);
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&a[i]);
        }
        a[n+1]=d;//n+1,注意
        l=1;r=d;
        int mid;
        while(l<=r)
        {
            mid=(l+r)>>1;
            if(pd(mid)) 
            {
                ans=mid;
                l=mid+1;
            }
            else r=mid-1;
        }
        printf("%d",ans);
        return 0;
    }

    P2985 [USACO10FEB]吃巧克力Chocolate Eating

    先二分求出最不开心那天的最大开心值ans

    再对ans重新走一遍以记录正确日期(这里大概最难吧

    直接代码

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define maxn 500010
    using namespace std;
    
    long long ans,day[maxn],l,r,mid,n,d,h[maxn];
    
    bool eat(long long x)
    {
        long long cnt=0,sum=0;
        for(int i=1;i<=d;i++)
        {
            sum=sum>>1;
            while(sum<x)
            {
                sum+=h[++cnt];//达不到x便返回
                if(cnt>n) return false;
                if(x&&x==ans) day[cnt]=i;//if保证仅对ans记录日期
            }
        }
        return true;
    }
    
    int main()
    {
        scanf("%lld%lld",&n,&d);
        for(int i=1;i<=n;i++)
        {
            scanf("%lld",&h[i]);
            r+=h[i];
        }
        while(l<=r)
        {
            mid=(l+r)>>1;
            if(eat(mid)) 
            {
                ans=mid;
                l=mid+1;
            }
            else r=mid-1;
        }
        printf("%lld
    ",ans);
        eat(ans);
        for(int i=1;i<=n;i++)
        {
            if(day[i]) printf("%lld
    ",day[i]);
            else printf("%lld
    ",d);//要吃完巧克力,剩下的就直接输出没吃的
        }
        return 0;
    }

    P1314 聪明的质监员要被开除的质检员

    在W取0时,所有的区间内的矿石都可以选上,

    而在W大于最大的质量时,所有的矿石都选不上。

    然后简单算一下就发现:

    W越大,矿石选的越少,W越小,矿石选的越多。

    所以w↑,y↓

    Y>s 时,需要增大WW来减小YY,从而|Y-s|变小;

    Y==s时,Ys==0;

    Y<s时,需要减小W来增大YY,从而|Y-s|变大;

    之后用前缀和去算区间val和,这样快。

    AC码:

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define maxn 200010
    #define ll long long
    using namespace std;
    
    ll ans,s;
    int n,m,w[maxn],v[maxn],l[maxn],r[maxn],maxx,minn=2147483647;
    ll qaqn[maxn],qaqv[maxn],tot,sum;
    
    bool pd(int ww)
    {
        tot=0,sum=0;
        memset(qaqn,0,sizeof(qaqn));
        memset(qaqv,0,sizeof(qaqv));
        for(int i=1;i<=n;i++)
        {
            if(w[i]>=ww)
            {
                qaqn[i]=qaqn[i-1]+1;
                qaqv[i]=qaqv[i-1]+v[i];
            }
            else 
            {
                qaqn[i]=qaqn[i-1];
                qaqv[i]=qaqv[i-1];
            }
        }
        for(int i=1;i<=m;i++)
        {
            tot+=(qaqn[r[i]]-qaqn[l[i]-1])*(qaqv[r[i]]-qaqv[l[i]-1]); 
        }
        sum+=llabs(tot-s);
        if(tot>s) return true;
        else return false;
    }
    
    int main()
    {
        scanf("%d%d%lld",&n,&m,&s);
        for(int i=1;i<=n;i++)
        {
            scanf("%d%d",&w[i],&v[i]);
            maxx=max(maxx,w[i]);
            minn=min(minn,w[i]);
        }
        for(int i=1;i<=m;i++)
        {
            scanf("%d%d",&l[i],&r[i]);
        }
        int ql=minn-1,qr=maxx+2,mid;
        ll ans=0x3f3f3f3f3f3f3f3f;
        while(ql<=qr)
        {
            mid=(ql+qr)>>1;
            if(pd(mid)) ql=mid+1;
            else qr=mid-1;
            ans=min(sum,ans);
        }
        printf("%lld",ans);
        return 0;
    }

    P2571 [SCOI2010]传送带

    三分套三分,把线段ab三分寻找转折点后从转折点跑向线段cd,然后再在线段cd上三分寻找抵达地点跑向点d。

    之后我陷入了一脸懵的状态,三分与a,c的距离,但是这个距离根本无法确定该点的坐标。

    之后看了大佬的题解后...啥!还可以三分比例!

    然后

     不是太理解。。。讲不出来,只好把那位dalao的话引用过来了

    我们现在已知线段AC,假设现在在AC上已找到一点E,我们要去计算E与另一条线段的距离,这个E的坐标怎么求呢?

    AC为斜边,作一个Rt△ABC。作ED⊥CB,此时我们可以发现,△AED和△ACB是相似三角形

    所以AEAC有一定的比值,即AC/AE=k(0k1)。

    所以我们可以直接三分k,然后就可以求出F的坐标了。

     AC码:

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<cmath>
    #define maxn 10010
    #define eps 1e-7
    using namespace std;
    
    double ax,ay,bx,by,cx,cy,dx,dy,P,Q,R;//其实可以用结构体来存四个点的坐标,这样后面的函数更方便写
    
    double dis(double x,double y,double xx,double yy)//又臭又长
    {
        return sqrt((x-xx)*(x-xx)+(y-yy)*(y-yy));
    }
    
    double ctx(double x,double xx,double k)
    {
        double qaq;
        qaq=(xx-x)*k+x;
        return qaq;
    }
    
    double cty(double y,double yy,double k)
    {
        double qwq;
        qwq=(yy-y)*k+y;
        return qwq;
    }
    
    double expd(double x,double y)
    {
        double nx1=ctx(ax,bx,x),ny1=cty(ay,by,x),nx2=ctx(cx,dx,y),ny2=cty(cy,dy,y);//这就是不用结构体的下场
        return dis(ax,ay,nx1,ny1)/P+dis(nx1,ny1,nx2,ny2)/R+dis(nx2,ny2,dx,dy)/Q;
    }
    
    double pd(double x)
    {
        double l=0.0,r=1.0;
        while(r-l>=eps)
        {
            double mid=l+(r-l)/3.0,mmid=r-(r-l)/3.0;
            if(expd(x,mid)>expd(x,mmid)) l=mid;
            else r=mmid;
        }
        return expd(x,l);
    }
    
    int main()
    {
        scanf("%lf%lf%lf%lf",&ax,&ay,&bx,&by);
        scanf("%lf%lf%lf%lf",&cx,&cy,&dx,&dy);
        scanf("%lf%lf%lf",&P,&Q,&R);
        double l=0.0,r=1.0;
        while(r-l>=eps)
        {
            double mid=l+(r-l)/3.0,mmid=r-(r-l)/3.0;
            if(pd(mid)>pd(mmid)) l=mid;
            else r=mmid;
        }
        printf("%.2lf",pd(l));
        return 0;
    }

     2019-08-01 23:42:18

  • 相关阅读:
    Java实现连接FTP服务并传递文件
    消息队列(MQ)入门-activemq,rocketmq代码级别
    js分页功能实现
    记录几个遇到的问题和解决方法
    oracle 日志归档设置
    打印系统时间
    linux 定时任务
    linux 安装jdk
    db2 命令
    二维码、条形码扫描——使用Google ZXing
  • 原文地址:https://www.cnblogs.com/plzplz/p/11286134.html
Copyright © 2020-2023  润新知