[NOIP2017模拟]最佳序列

2017.11.2 T2 2029

样例数据
输入

3 2 3
6 2 8

输出

5.3333

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

代码
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;
}

本题结。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值