[NOIP2017模拟]最佳序列

来源:互联网 发布:灯火阑珊网络电视直播 编辑:程序博客网 时间:2024/06/01 07:12

2017.11.2 T2 2029

样例数据
输入

3 2 3
6 2 8

输出

5.3333

分析:这道题我只会扫描所有的区间输出答案这种暴力,也就是说复杂度是O((rl)N),复杂度与区间有关,结果数据水,40%成功水成90%,而且如果进行优化,还能AC!
正解是单调队列优化:
二分平均数,将每个数都减去一个平均数,然后记录前缀和,如果在长为l到r的所有区间中有前缀和大于0的说明就可以到这个平均数,没有就说明不能到平均数,单调队列能让这个过程变成O(N)的。

代码
100%:暴力+优化

#include<iostream>#include<cstdio>#include<cstdlib>#include<cstring>#include<string>#include<ctime>#include<cmath>#include<algorithm>#include<cctype>#include<iomanip>#include<queue>#include<set>using namespace std;inline int getint(){    int sum=0,f=1;    char ch;    for(ch=getchar();!isdigit(ch)&&ch!='-';ch=getchar());    if(ch=='-')    {        f=-1;        ch=getchar();    }    for(;isdigit(ch);ch=getchar())        sum=(sum<<3)+(sum<<1)+ch-48;    return sum*f;}const int maxn=20005;const double eps=1e-9;int n,l,r,a[maxn],maxa;double ans,sum[maxn];inline void solve(){    for(register int i=l;i<=min(l*2,r);++i)//这是个优化//如果是一个数的区间,显然取最大的那个数就行,它和任意数取平均数都会比本身小//同理,如果找到了长为l的平均最大的区间,显然与其他长为l的区间合并求平均数都会比他小,所以只需要求长为l到长为2l的区间的最大值就完了//现在想来觉得有点小bug啊......    {        if(sum[i]/i-ans>eps)            ans=sum[i]/i;        for(register int j=i+1;j<=n;++j)            if((sum[j]-sum[j-i])/i-ans>eps)                ans=(sum[j]-sum[j-i])/i;    }    printf("%0.4f\n",ans);}int main(){    freopen("seq.in","r",stdin);    freopen("seq.out","w",stdout);    n=getint(),l=getint(),r=getint();    for(register int i=1;i<=n;++i)    {        a[i]=getint();        sum[i]=sum[i-1]+a[i];//记录前缀和    }    solve();    return 0;}

100%:单调队列优化

#include<iostream>#include<cstdio>#include<cstdlib>#include<cstring>#include<string>#include<ctime>#include<cmath>#include<algorithm>#include<cctype>#include<iomanip>#include<deque>#include<set>using namespace std;int getint(){    int sum=0,f=1;    char ch;    for(ch=getchar();!isdigit(ch)&&ch!='-';ch=getchar());    if(ch=='-')    {        f=-1;        ch=getchar();    }    for(;isdigit(ch);ch=getchar())        sum=(sum<<3)+(sum<<1)+ch-48;    return sum*f;}const int maxn=20010;const double eps=1e-9;int n,l,r;double w,ans,a[maxn],maxa,b[maxn],sum[maxn];bool check(double x){    for(int i=1;i<=n;++i) b[i]=a[i]-x;//把所有的数减去二分的这个平均数    for(int i=1;i<=n;++i) sum[i]=sum[i-1]+b[i];//记录前缀和    deque<int> que;    for(int i=l;i<=r-1;++i)//先把长为l到r-1的前缀和放到数组里(只能是单调队列,小于的都删掉)    {        while(!que.empty()&&sum[i]>sum[que.back()])            que.pop_back();        que.push_back(i);    }    for(int i=1;i<=n-l+1;++i)//固定左端点    {        while(!que.empty()&&que.front()<i+l-1)            que.pop_front();//删除队顶与左端点相距小于的l的点(可能队列中还有,但是单调队列只关注最大的),保证队顶的数是满足在左端点右侧l到r的距离内的        if(i+r-1<=n)        {            while(!que.empty()&&sum[i+r-1]>sum[que.back()])//加入新的,把小于它的都删掉                que.pop_back();            que.push_back(i+r-1);        }        if(sum[que.front()]-sum[i-1]>=0)//判断队列中(队列中的都是满足在区间里的)最大的能否让前缀和大于0,满足了就return true            return true;    }    return false;}int main(){    freopen("seq.in","r",stdin);    freopen("seq.out","w",stdout);    n=getint(),l=getint(),r=getint();    for(int i=1;i<=n;++i)    {        scanf("%lf",&a[i]);        if(a[i]-maxa>eps)            maxa=a[i];    }    double l=0,r=maxa,mid;    while(r-l>eps)//二分查找平均数    {        mid=(l+r)/2;        if(check(mid))            l=mid,ans=mid;        else            r=mid;    }    printf("%0.4f\n",ans);    return 0;}

本题结。

原创粉丝点击