二分与三分(精度类型)

Posted zhangjianjunab

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了二分与三分(精度类型)相关的知识,希望对你有一定的参考价值。

二分:传送门
三分:传送门
(注意,是五舍六入,不是四舍五入,在2018年10月23日前是这样的)
话说一本通上不是有讲嘛,做法自己看吧。。。(但是我太弱了,精度版看不懂QWQ)。

简单讲一下二分与三分吧。

二分:必须满足单调性:
技术分享图片

非增或非减就叫单调性(如果就好几个数相同,一般会用二分来找第一个数或最后一个数)。

我们用两个数字l与r来代表搜索范围,而mid代表中间的位置的值,来跳来跳去,看情况来写。

这题

int  l=1,r=n,mid,ans=0;
while(l<=r)
{
    mid=(l+r)/2;
    if(a[mid]<=m)l=mid+1,ans=mid;
    else  if(a[mid]>m)r=mid-1;
}
printf("%d
",ans);

当然,还可以用二分来二分答案,比如你要用某种方法,但是缺少一种条件,你又意外发现这个条件越大或越小,会让你方法越容易达到目标(也就是发现了二分性),就可以二分这个条件,不断丢给这个方法,让他运行。

这题

//发现这道题如果二分总和,总和越大,分的段数就越少,越容易小于等于m,也就越容易达到目标,因此得出做法
#include<cstdio>
#include<cstring>
using  namespace  std;
int  a[210000],b[210000],n,m;
bool  pd(int  x)
{
    int  kk=1/*自行理解*/,ans=0;
    for(int  i=1;i<=n;i++)
    {
        if(a[i]>x)return  false;//如果有一个数超过了,就退出
        if(ans+a[i]<=x)ans+=a[i];//加上
        else
        {
            kk++;/*统计答案*/if(kk>m)return  false;//分的段数过多,退出
            ans=a[i];//重置
        }
    }
    return  true;
}
int  main()
{
    int  sum=0;
    scanf("%d%d",&n,&m);
    for(int  i=1;i<=n;i++)scanf("%d",&a[i]),sum+=a[i];
    int  l=1/*这里可以改成a数组的最大值)*/,r=sum/*统计所有的和*/,mid,x;
    while(l<=r)
    {
        mid=(l+r)/2;
        if(pd(mid)==true)//如果可以,代表可以让r再收拢一点
        {
            r=mid-1;x=mid;
        }
        else  l=mid+1;//不行,则扩宽l的限制
    }
    printf("%d
",x);
    return  0;
}

(我不是标题狗)

但是开头的那道二分题,是要用精度的!!!

看别人的代码,也都是大把大把的double,丑陋的代码:

#include<cstdio>
#include<cstring>
using  namespace  std;
double  a[210000],sum[210000],b[210000];
int  n,L;
double  mymin(double  x,double  y){return  x<y?x:y;}
double  mymax(double  x,double  y){return  x>y?x:y;}
bool  check(double  x)//这个上网搜搜都是有的
{
    double  min_val=999999999.0,ans=-99999999.0;
    for(int  i=1;i<=n;i++)b[i]=a[i]-x,sum[i]=b[i]+sum[i-1];
    for(int  i=L;i<=n;i++)//用DP搜索一段长度大于等于L的子串的最大和
    {
        min_val=mymin(min_val,sum[i-L]);
        ans=mymax(ans,sum[i]-min_val);
    }
    return  ans>=0.0;//猴子已经死机。。。上网搜吧。。。
}
int  main()
{
    scanf("%d%d",&n,&L);
    for(int  i=1;i<=n;i++)scanf("%lf",&a[i]);
    double  l=-1e6,r=1e6,mid,ans=0.0,jie=1e-5;//猴子已死机。。。
    while(r-l>jie)
    {
        mid=(l+r)/2;
        if(check(mid))ans=mid,l=mid;
        else  r=mid;
    }
    printf("%d
",int(r*1000.0));
    return  0;
}

---


还是上网弄了一个下来

这题可以用斜率优化做也可以用二分做,我用的是二分做法。

题意:给你n个牛的自身价值,让你找出连续的且数量大于等于F的一段区间,使这段区间内的牛的平均价值最大。

思路:用二分枚举平均值ave,每个牛的价值都减去ave,看是否有连续的超过f长度的区间使得这段区间的价值大于等于0,如果能找到,那么说明这个平均值可以达到。先每个a[i]减去ave得到b[i],用dp[i]表示以i为结尾区间连续长度大于等于f的最大连续区间和,maxx[i]表示以i为结尾的最大连续区间和,sum[i]表示1~i的价值总和那么maxx[i]=max(maxx[i-1]+b[i],b[i]),dp[i]=maxx[i-f+1]+sum[i]-sum[i-f+1],判断是否有一个i(i>=f)满足dp[i]>=0.

作者:Herumw
来源:CSDN
原文:https://blog.csdn.net/kirito_acmer/article/details/48716719
版权声明:本文为博主原创文章,转载请附上博文链接!

------

---

如果你是神犇看懂了=_=。哥哥,我们不约

好的,如果你不是神犇,那么请:
技术分享图片

不过,有时,我们不一定要用double,我们乘以一个1000转成int,然后在check再暂时转回double

代码(摘自我机房大佬CLB的代码):

#include<cstdio>
#include<cstring>
using  namespace  std;
double  a[210000],sum[210000],b[210000];
int  n,L;
double  mymin(double  x,double  y){return  x<y?x:y;}
double  mymax(double  x,double  y){return  x>y?x:y;}
bool  check(double  x)
{
    double  min_val=999999999.0,ans=-99999999.0;
    for(int  i=1;i<=n;i++)b[i]=a[i]-x,sum[i]=b[i]+sum[i-1];
    for(int  i=L;i<=n;i++)
    {
        min_val=mymin(min_val,sum[i-L]);
        ans=mymax(ans,sum[i]-min_val);
    }
    return  ans>=0.0;
}
int  main()
{
    scanf("%d%d",&n,&L);
    for(int  i=1;i<=n;i++)scanf("%lf",&a[i]);
    double  l=-1e6,r=1e6,mid,ans=0.0,jie=1e-5;
    while(r-l>jie)
    {
        mid=(l+r)/2;
        if(check(mid))ans=mid,l=mid;
        else  r=mid;
    }
    printf("%d
",int(r*1000.0));
    return  0;
}

到三分了,三分呢,主要解决单峰问题(求单峰),不过递增或递减也可以哟不过只是求第一个数或最后一个数

所谓三分,肯定是把区间用两个数(记作m1与m2,m1<=m2)分成三个部分(除了某些特殊情况:n=2...),然后将这两个数比较,然后l跳到m1+1,r跳到m2-1,然后当l>r退出,由于这里比二分还复杂,所以猴子还没找到直接记录答案的方法,只能最后比较l与r,虽然一次只缩小[1/3]

但是总比某退火的玄学复杂度快吧。

举个栗子(二次函数:开口向上):

技术分享图片

以x轴做三分,那么m1(A点)的y小于m2(B点)的y,那么我们就让r跳到比m2小一点的地方(按题目来定),反之让l跳到比m1大一点点的位置,来达到我们将l与r缩小的目的。
至于突然退化成一次函数的二次函数(某毒瘤出题人干的),与二次函数的情况一样,不过l或r有一个不变罢了。。。

伪例题:

一个序列,其中有一个数,这个数左边的序列严格递增,左边严格递减,右边严格递增。

一个整数n
n个数字

输出这个数字:

样例输入:
5
1 2 3 2 1
样例输出:
3

int  l=1,r=n,m1,m2;
while(l<=r)
{
    m1=l+(r-l+1/*+1不+1都可以*/)/3/*为什么不+1?如果l==r,m1就跳出去了,m2也是同理*/;m2=r-(r-l+1)/3;
    if(a[m1]<=a[m2]/*<与<=都可以*/)r=m2-1;
    else  l=m1+1;//缩小范围
}
if(a[l]<a[r])printf("%d
",a[l]);
else  printf("%d
",a[r]);

至于如果有一段的值相等,这种情况我认为是可以的,欢迎大家再我的下方评论,毕竟三分刚学不久。。。

技术分享图片
比如上图。。。

然后,又到了开头的那道三分了。。。

又是精度问题!

至于单峰性。。。


题目:给你n条开口向上的二次曲线Si(a>0),定义F(x) = max(Si(x)),求F(x)的最小值。

分析:三分。F(x)是一个单峰函数,先单调递减后单调递增,利用三分求最小值。

        首先,证明两个二次函数构造的F2(x)为单峰函数;

        (如果不成立,则存在两个连续的波谷,交点处一个函数递增另一个递减,矛盾,不会取递减函数)

        然后,用数学归纳法证明,Fi(x)为单峰函数,则Fi+1 = max(Fi(x),Si+1(x))也是单峰函数;

        (如果存在两个(或更多)连续的波谷,交点处一个函数递增另一个递减,矛盾,所以只有一个波谷)

        结论,综上所述得证结论,只存在一个波谷。

作者:小白菜又菜
来源:CSDN
原文:https://blog.csdn.net/mobius_strip/article/details/45618095
版权声明:本文为博主原创文章,转载请附上博文链接!


看不懂自己YY吧,啊啊啊啊。

难道又要动用我们毒瘤可爱的double了?
不,我拒绝!!!

既然保留四位小数,又五舍六入,那么乘100000啦!

#include<cstdio>
#include<cstring>
using  namespace  std;
typedef  long  long  ll;
ll  a[200000],b[200000],c[200000],n;
inline  double  mymax(double  x,double  y){return  x>y?x:y;}
inline  double  cai(ll  x)//从所有函数中选最大值 
{
    double  xx=x/100000.0;//变回double
    double  mmax=-999999999;
    for(ll  i=1;i<=n;i++)mmax=mymax(mmax,(a[i]*1.0)*xx*xx+(b[i]*1.0)*xx+(c[i]*1.0));
    return  mmax;
}
int  main()
{
    ll  T;scanf("%lld",&T);
    while(T--)
    {
        scanf("%lld",&n);
        for(int  i=1;i<=n;i++)scanf("%lld%lld%lld",&a[i],&b[i],&c[i]);
        ll  l=0,r=1e8/*1*10的8
        次方*/,m1,m2,ans;//三分日常操作 
        while(l<=r)
        {
            m1=l+(r-l+1)/3;m2=r-(r-l+1)/3;
            if(cai(m1)<=cai(m2))r=m2-1;
            else  l=m1+1;
        }
        if(cai(l)<cai(r))printf("%.4lf
",cai(l));
        else  printf("%.4lf
",cai(r));//统计答案 
    }
    return  0;
}

技术分享图片

。。。
作者可能学了个假的三分。。。

不过,如果乘以一百万(多乘了个10)。。。
技术分享图片
猴子(作者)想了一个坏想法,于是我用了高精度1e10与1e11,终于,在1e11时卡精度AC了!

AC代码:

//猴子将double*1e8将他转为long  long
#include<cstdio>
#include<cstring>
using  namespace  std;
typedef  long  long  ll;
ll  a[200000],b[200000],c[200000],n;
inline  double  mymax(double  x,double  y){return  x>y?x:y;}
inline  double  cai(ll  x)//转回long long;
{
    double  xx=x/100000000.0;
    double  mmax=-999999999;
    for(ll  i=1;i<=n;i++)mmax=mymax(mmax,(a[i]*1.0)*xx*xx+(b[i]*1.0)*xx+(c[i]*1.0));
    return  mmax;
}
int  main()
{
    ll  T;scanf("%lld",&T);
    while(T--)
    {
        scanf("%lld",&n);
        for(int  i=1;i<=n;i++)scanf("%lld%lld%lld",&a[i],&b[i],&c[i]);
        ll  l=0,r=1e11,m1,m2,ans;
        while(l<=r)
        {
            m1=l+(r-l+1)/3;m2=r-(r-l+1)/3;
            if(cai(m1)<cai(m2))r=m2-1;
            else  l=m1+1;//三分
        }
        if(cai(l)<cai(r))printf("%.4lf
",cai(l));
        else  printf("%.4lf
",cai(r));
    }
    return  0;
}

所以,带精度的二分与三分是可以转成long long来做的,在check里再转会double就行了,不过三分可能要多乘一点。

终于写完了。

每日笑话:

追到我的女神 我用了三个办法 办法一 坚持 办法二 不要脸 办法三 坚持不要脸 她带我回家 她爸爸很无礼地跟我说 我养了我女儿二十年 我凭什么把她嫁给你 我回答 你养她二十年 我要养她四十年 还要照顾你三十年 你凭什么不把她嫁给我
--------来源

技术分享图片

--------来源

感谢大家观看。
























以上是关于二分与三分(精度类型)的主要内容,如果未能解决你的问题,请参考以下文章

二分与三分

二分与三分

二分与三分

信息学奥赛一本通 1.2 二分与三分

二分与三分

二分与三分