https://i.loli.net/2019/07/25/5d39b5315c60935716.jpg

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\n",ans);
    eat(ans);
    for(int i=1;i<=n;i++)
    {
        if(day[i]) printf("%lld\n",day[i]);
        else printf("%lld\n",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

posted @ 2019-08-01 23:43  plzplz  阅读(121)  评论(0编辑  收藏  举报
https://i.loli.net/2019/07/25/5d39c1d4c249939054.jpg