洛谷-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
说明
【数据范围】
对于30%的数据有n<=1000。
对于100%的数据有n<=100000,1<=S<=T<=n,-10000<=价值指数<=10000。
【题目来源】
tinylic改编
题意:
有一个长度为n的数列,找出其中长度在s-t内的平均值最大数列,输出最大平均值。
题解:
一看题目,开始想到了移动窗口,结果TLE了。
之后想到了二分,但是也WA和TLE了几个点。
二分思想很简单,这道题主要学习的是这个判断函数check:
对于区间内的所有值我们可以都先减去初始平均值m,之后再判断若是在题目要求的范围内存在区间的和大于0,则证明存在更大的平均值,反之则平均值取大了,没有一个区间能够产生这么大的平均值。
这里用到了前缀和来记录区间内的和。
附上AC代码:

#include <iostream>
#include <iomanip>
using namespace std;
int n,s,t,a[100005],q[100005];//q数组用来记录前缀和的下标
double sum[100005];//前缀和记录到第i个的减去平均值的和
bool check(double m)
{
    sum[0]=0;
    for(int i=1;i<=n;i++)
    {
        sum[i]=sum[i-1]+a[i]-m;//初始化
    }
    int head=1,tail=0;//head为区间的左端点,tail为区间的右端点。
    for(int i=1;i<=n;i++)
    {
        if(i>=s)
        {
            while(head<=tail&&sum[i-s]<sum[q[tail]])//一直减小右端点,直到左端点大于右端点,或者在右端点处的前缀和小于sum【i-s】
                tail--;//单调队列维护
            q[++tail]=i-s;//对于每一个i都将他的右端点初始化为i-s;
        }
        if(head<=tail&&q[head]<i-t)//左端点小于上限,左端点加一;
            head++;
        if(head<=tail&&sum[i]-sum[q[head]]>=0)//如果这一段区间内的和大于0,则证明有更大的平均值存在。
            return true;
    }
    return false;
}
int main()
{
    cin>>n>>s>>t;
    for(int i=1;i<=n;++i)
    {
        cin>>a[i];
    }
    double l=-10005,m,r=10005;
    while(l+1e-5<r)
    {
        m=(l+r)/2;
        if(check(m))
            l=m;
        else
            r=m;
    }
    cout <<fixed<<setprecision(3)<<l<< endl;//输出要求格式为小数点后三位
    return 0;
}

欢迎评论!

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值