标签:限制 表示 name cstring ring 传送门 定义 小白 check
二分:传送门
三分:传送门
(注意,是五舍六入,不是四舍五入,在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的一段区间,使这段区间内的牛的平均价值最大。
作者: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\n",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\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就行了,不过三分可能要多乘一点。
终于写完了。
每日笑话:
追到我的女神 我用了三个办法 办法一 坚持 办法二 不要脸 办法三 坚持不要脸 她带我回家 她爸爸很无礼地跟我说 我养了我女儿二十年 我凭什么把她嫁给你 我回答 你养她二十年 我要养她四十年 还要照顾你三十年 你凭什么不把她嫁给我
--------来源
--------来源
感谢大家观看。
标签:限制 表示 name cstring ring 传送门 定义 小白 check
原文地址:https://www.cnblogs.com/zhangjianjunab/p/9841479.html