【洛谷】P1419 寻找段落

11 篇文章 0 订阅
3 篇文章 0 订阅

洛谷P1419 寻找段落

题目描述
给定一个长度为n的序列a_i,定义a[i]为第i个元素的价值。现在需要找出序列中最有价值的“段落”。段落的定义是长度在[S,T]之间的连续序列。最有价值段落是指平均值最大的段落,

段落的平均值=段落总价值/段落长度。

输入格式
第一行一个整数n,表示序列长度。

第二行两个整数S和T,表示段落长度的范围,在[S,T]之间。

第三行到第n+2行,每行一个整数表示每个元素的价值指数。

输出格式
一个实数,保留3位小数,表示最优段落的平均值。

输入输出样例
输入 #1

3
2 2
3
-1
2

输出 #1

1.000

说明/提示
【数据范围】

对于100%的数据有n<=100000,1<=S<=T<=n,-10000<=价值指数<=10000。

题目的大意是,给n个段落的价值,现在要画出一个长度为S到T的区间,使得这个区间内价值平均值最大。看样子是个滑动窗口,但这个区间长度不定又不知道如何处理。

注意到最后是保留三位小数,不禁想到可能是二分答案!答案的单调性是显然的。那么对于一个特定的均值,如何检查是否存在一个长度在S到T的区间满足呢?

假设当前需要判断的均值为x。首先为了简化问题,可以将数组里每个元素减掉x,这样就只需要看是否存在一个区间的和大于0即可。我们知道,滑动窗口处理区间的最大值和最小值可以达到 O ( n ) O(n) O(n),而区间和可以转化成前缀和的差。检查区间和的最大值是否能够大于等于0,就等价于检查s[i]与之前S到T范围内的最小值s之差,是否大于等于0。代码如下:

  • a是原数组,s是前缀和
  • 二分的区间是价值最低的段落到价值最高的段落
  • 这里没有直接在数组上减去mid,而是在前缀和里减,因为之后只需要用到前缀和。注意s要开double。。否则-mid就尴尬了,debug了一会这个问题。。
  • q里存的是从i之前S到T范围的前缀和s形成的单调队列(开始特别难以理解q里存的是什么。因为现在是要看是否存在s[i]-s[j]非负,s[i]是当前遍历到i可以直接求到的,而s[j]是要i之前S到T区间内的前缀和,这里只需要取最小值,这正是滑动窗口最小值问题。。)
#include <bits/stdc++.h>
using namespace std;
int n,S,T;
double a[100010],s[100010];
int f(double mid){
    s[0]=0;
    for(int i=1;i<=n;i++) s[i]=s[i-1]+a[i]-mid;
    deque<int> q;
    for(int i=1;i<=n;i++){
        if(i>=S){
            while(!q.empty()&&s[i-S]<s[q.back()]) q.pop_back();
            q.push_back(i-S);
        }
        if(!q.empty()&&q.front()<i-T) q.pop_front();
        if(!q.empty()&&s[i]-s[q.front()]>=0) return 1;
    }
    return 0;
}
int main(){
    cin>>n>>S>>T;
    double minv=INT_MAX,maxv=INT_MIN;
    for(int i=1;i<=n;i++){
        cin>>a[i];
        minv=min(minv,a[i]);
        maxv=max(maxv,a[i]);
    }
    double l=minv,r=maxv;
    while(r-l>1e-5){
        double mid=(l+r)/2;
        if(f(mid)) l=mid;
        else r=mid;
    }
    printf("%.3lf",l);
    return 0;
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值