ACcoders Problem 2044 题解

文章介绍了如何使用二分搜索算法找到一个长度为n的序列中,长度不小于m的连续子序列的最大平均值。通过维护前缀和,将问题转化为判断每个子序列元素减去最大平均值的和是否非负,从而优化了搜索过程。在代码实现中,通过不断调整二分查找的边界,最终得到答案并乘以1000以满足题目的要求。
摘要由CSDN通过智能技术生成

题意

给出一个长度为 n n n 的序列 a a a,求平均值最大的且长度不小于 m m m 的连续子序列的平均值(答案乘 1000 1000 1000)。

思路

考虑二分平均值最大的连续子序列的平均值。

我们将左右端点分别设为题目之中的数据范围(注意,变量为分数,用 double \text{double} double),然后再二分。

考虑维护一个前缀和数组,用来计算平均值。如果当前连续子序列平均值 s u m k \frac{sum}{k} ksum s u m sum sum 为子序列和, k k k 为长度)大于等于当前二分的最大平均值 m i d mid mid,则代表当前最大平均值还可以更大,更新左端点。否则,代表当前最大平均值太大了,更新右端点。

如果这样进行二分的话,我们要同时枚举 k k k 以及子序列和 s u m sum sum,一定会死得很惨。

那该怎么办呢?

我们将一开始列出的式子 s u m k ≥ m i d \frac{sum}{k}\ge mid ksummid 化简一下,可得:

s u m − k × m i d ≥ 0 sum-k\times mid\ge 0 sumk×mid0

再将我们再考虑, s u m sum sum 可以拆分成 k k k a i a_{i} ai 之和,再将其结合,又得:

k ( a i − m i d ) ≥ 0 ( i ∈ [ 1 , k ] ) k(a_{i}-mid)\ge 0 (i\in[1,k]) k(aimid)0(i[1,k])

此式意义为每个长度为 k k k 的连续子序列的元素减最大子序列的平均值的和。如果 a i − m i d ≥ 0 a_{i}-mid\ge 0 aimid0,也就说明 m i d mid mid 还可以更大,同上。

我们可以先用一个 s u m sum sum 数组来预处理出连续子序列的元素减最大子序列的平均值的和,但是 k k k 我们并不知道,那就直接枚举。考虑从最小合法的右端点 m m m 枚举右端点,左端点也就应该再 r − m r-m rm 的左边。

那么该如何来判断如何二分呢?显然,我们要使前式尽可能的大(其实满足前式大于 0 0 0 即可),也就是说,需要让 s u m r − s u m l − 1 sum_{r}-sum_{l-1} sumrsuml1 尽可能的大。因为 r r r 现在已经是固定的了,所以只需要让 s u m l − 1 sum_{l-1} suml1 尽可能的小即可。考虑用一个 min ⁡ \min min 记录最小的 s u m l − 1 sum_{l-1} suml1,如果满足前式条件,就表示 m i d mid mid 还可能更大,否则, m i d mid mid 就太大了。

代码

#include<bits/stdc++.h>
using namespace std;
double a[1000001];
double b[1000001];
double s[1000001];
int n,m;
bool check(double x)
{
    double minx=11451414.0;
    for(int i=1;i<=n;i++) b[i]=a[i]-x;
    for(int i=1;i<=n;i++) s[i]=s[i-1]+b[i];
    for(int i=m;i<=n;i++)
    {
        minx=min(minx,s[i-m]);
        if(s[i]-minx>=0) return 1;
    } 
    return 0;
}
int main()
{
    scanf("%d %d",&n,&m);
    for(int i=1;i<=n;i++)
        scanf("%lf",&a[i]);
    double l=-100000.0,r=100000.0;
    while(r-l>0.000001)
    {
        double mid=(l+r)/2;
        if(check(mid)) l=mid;
        else r=mid;
    }
    printf("%d",int(r*1000.0));
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值