二分与三分(精度类型)

二分:传送门
三分:传送门
(注意,是五舍六入,不是四舍五入,在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\n",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\n",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\n",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\n",ans);
	return 0;
}

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

所谓三分,肯定是把区间用两个数(记作m1与m2,m1<=m2)分成三个部分(除了某些特殊情况:n=2…),然后将这两个数比较,然后l跳到m1+1,r跳到m2-1,然后当l>r退出,由于这里比二分还复杂,所以猴子还没找到直接记录答案的方法,只能最后比较l与r,虽然一次只缩小 1 / 3 1/3 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\n",a[l]);
else  printf("%d\n",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\n",cai(l));
		else  printf("%.4lf\n",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\n",cai(l));
		else  printf("%.4lf\n",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\n",cai(r/*可以换成l*/));
	}
	return  0;
}
//开心! 

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

    原文作者:算法
    原文地址: https://www.twblogs.net/a/5bd3a3992b717778ac20a095
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞