NOIP模拟 最佳序列【二分答案+线段树(单调队列)】

题目大意:

给出一个长度为n的序列,求所有长度在[L,R]中的子段的平均值的最大值。(1<=n<=100000)。

解题思路:

考试时发现平均值根本没有规律可言,所以想到了二分答案,就变为了判定性问题。

设二分答案为x,然后将所有数减去x,那如果有一个合法区间和大等于0,则说明该平均值较小,反之则较大。

那如何判定呢?对于一个左端点i,则合法区间右端点j在[i+L-1,i+R-1]之间,该区间和为sum[j]-sum[i-1](前缀和),而sum[i]已决定,我们只要找到sum[j]的最大值,看sum[j]-sum[i-1]是否大等于0,所以可以用线段树或单调队列维护所有前缀和即可。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
#define ll long long
using namespace std;

int getint()
{
    int i=0,f=1;char c;
    for(c=getchar();(c<'0'||c>'9')&&c!='-';c=getchar());
    if(c=='-')f=-1,c=getchar();
    for(;c>='0'&&c<='9';c=getchar())i=(i<<3)+(i<<1)+c-'0';
    return i*f;
}

const int N=20005;
const double eps=1e-7,INF=1e18;
int n,L,R,a[N];
double sum[N],tr[N<<2];

void build(int k,int l,int r)
{
    if(l==r)
    {
        tr[k]=sum[l];
        return;
    }
    int mid=l+r>>1;
    build(k<<1,l,mid),build(k<<1|1,mid+1,r);
    tr[k]=max(tr[k<<1],tr[k<<1|1]);
}

double query(int k,int l,int r,int x,int y)
{
    if(l>r||x>y)return -INF;
    if(x<=l&&r<=y)return tr[k];
    int mid=l+r>>1;
    if(y<=mid)return query(k<<1,l,mid,x,y);
    else if(x>mid)return query(k<<1|1,mid+1,r,x,y);
    else return max(query(k<<1,l,mid,x,mid),query(k<<1|1,mid+1,r,mid+1,y));
}

bool check(double num)
{
    for(int i=1;i<=n;i++)sum[i]=sum[i-1]+a[i]-num;
    build(1,1,n);
    for(int i=1;i<=n;i++)
    {
        double tmp=query(1,1,n,i+L-1,min(n,i+R-1));
        if(tmp-sum[i-1]>=0)return true;
    }
    return false;
}


int main()
{
    //freopen("seq.in","r",stdin);
    //freopen("seq.out","w",stdout);
    n=getint(),L=getint(),R=getint();
    double l=INF,r=0,ans;
    for(int i=1;i<=n;i++)
    {
        a[i]=getint();
        l=min(l,(double)a[i]);
        r=max(r,(double)a[i]);
    }
    while(abs(r-l)>eps)
    {
        double mid=(l+r)*1.0/2;
        if(check(mid))ans=mid,l=mid;
        else r=mid;
    }
    printf("%0.4lf",ans);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值