题意
给出一个长度为 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 ksum≥mid 化简一下,可得:
s u m − k × m i d ≥ 0 sum-k\times mid\ge 0 sum−k×mid≥0
再将我们再考虑, 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(ai−mid)≥0(i∈[1,k])
此式意义为每个长度为 k k k 的连续子序列的元素减最大子序列的平均值的和。如果 a i − m i d ≥ 0 a_{i}-mid\ge 0 ai−mid≥0,也就说明 m i d mid mid 还可以更大,同上。
我们可以先用一个 s u m sum sum 数组来预处理出连续子序列的元素减最大子序列的平均值的和,但是 k k k 我们并不知道,那就直接枚举。考虑从最小合法的右端点 m m m 枚举右端点,左端点也就应该再 r − m r-m r−m 的左边。
那么该如何来判断如何二分呢?显然,我们要使前式尽可能的大(其实满足前式大于 0 0 0 即可),也就是说,需要让 s u m r − s u m l − 1 sum_{r}-sum_{l-1} sumr−suml−1 尽可能的大。因为 r r r 现在已经是固定的了,所以只需要让 s u m l − 1 sum_{l-1} suml−1 尽可能的小即可。考虑用一个 min \min min 记录最小的 s u m l − 1 sum_{l-1} suml−1,如果满足前式条件,就表示 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;
}