zoukankan      html  css  js  c++  java
  • 二分与三分(精度类型)

    引子

    二分:传送门
    三分:传送门
    (注意,是五舍六入,不是四舍五入,在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的代码):

    //Sorry,之前放错代码了
    #include<cstdio>
    #include<cstring>
    #define INF 2147483647
    using namespace std;
    int N,L,a[110000];
    long long sum[110000];
    inline bool check(int x)
    {
    	for(int i=1;i<=N;i++) sum[i]=sum[i-1]+(a[i]-x);//减去平均值,看看总和是否能为非负数 
    	long long minn=INF;
    	for(int i=L;i<=N;i++)
    	{
    		if(sum[i-L]<minn) minn=sum[i-L];
    		if(sum[i]-minn>=0) return true;//这里是把拖后腿那一部分减掉,如果为正数,就表示此方案可行 
    	}
    	return false;//此方案不行 
    	
    }
    int main()
    {
    	scanf("%d%d",&N,&L);
    	for(int i=1;i<=N;i++) scanf("%d",&a[i]),a[i]*=1000;//最后结果要乘1000,为了方便计算(不算小数),就在开始直接乘了 
    	int l,r,mid,ans;
    	l=0;r=2000000;
    	while(l<=r)//二分平均值 
    	{
    		mid=(l+r)>>1;
    		if(check(mid)==true) l=mid+1,ans=mid;
    		else r=mid-1;
    	}
    	printf("%d
    ",ans);
    	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就行了,不过三分可能要多乘一点。

    终于写完了。

    每日笑话:

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

    在这里插入图片描述

    --------来源

    感谢大家观看。

    就怪了!

    难道大家没发现三分会比二分慢吗?
    啊啊啊啊啊!
    但是,我们可以将m1=(l+r)/2;m2=m1+1;
    这样子,如果比较完后,l=m2或者r=m1,正确性的话虽然m1与m2靠的很近,不过依旧可以达到单峰在l与r之间,就对了,不过是l<r不是l<=r,因为这样分m1与m2严格m1<m2且m1与m2属于[l,r],所以只能用l<r,不过这样有个好处,就是由于l或r都是等于m1或m2的,所以不会出现r<l的情况,最后一定l==r,所以答案就是l或r,而且一次缩小的区间变大了,常数也就小了!

    代码:

    #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/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)>>1;m2=m1+1;
    			if(cai(m1)<cai(m2))r=m1;
    			else  l=m2;
    		}
    		printf("%.4lf
    ",cai(r/*可以换成l*/));
    	}
    	return  0;
    }
    //开心! 
    

    是不是意味着我们可以出毒瘤卡常题目了

    小结

    其实控制精度大小还有种方法,就是控制迭代的次数。

    学习https://www.luogu.org/blog/3-3-7-6-2-9-9-3-1-5/ru-he-zhao-dan-feng-han-shuo-feng-zhi:

    学习不稳定的三分,以及用二分做三分。

    高中补求导二分。

  • 相关阅读:
    angularjs学习笔记一之显示数据,修改数据
    收藏/不再提醒
    CSS3动画
    Content-Type
    键盘快捷键
    url、href、src 详解
    关于docnment.write() 会清空原来的内容
    jq事件注意点
    echart的自适应
    键盘事件
  • 原文地址:https://www.cnblogs.com/zhangjianjunab/p/9841479.html
Copyright © 2011-2022 走看看