YBTOJ:最大均值
题目分析
答案具有单调性,先确定答案的上下限,由于是部分或全部\(a_i\)的平均值,且所有的\(a_i\)都小于\(2000\),所以答案范围为\(0\leq x\leq 2000\)
考虑二分,判定条件是“是否存在一个长度\(\leq L\)的子段,平均值不小于\(mid\)”
求平均值十分麻烦,我们可以把子段内的所有项都减去\(mid\),于是平均值便不小于\(0\),所以说该减去了\(mid\)子段和非负。
所以判断条件转化成“是否存在一个长度不小于\(L\)的子段,子段和非负”。
子段和可以转化成前缀和相减的形式,即 $$\sum_{i=l}^{i=r}a_i = \sum_{i=1}^{i=r}a_i-\sum _{i=1}^{i=l}a_i$$
代入到此题,可以看出
\[max_{i-j \leq L}(\sum_{j+1}^i a)=max_{L \leq i \leq n}(pre_i-min_{0 \leq j \leq i-L}(pre_j))
\]
有亿点点复杂……
仔细观察,理解之后,发现在枚举\(i\)的过程中,\(j\)的取值范围\(0 \sim i-L\)每次只会增大\(1\)。通俗地说,就是每次只有一个新值进入\(min_{0 \leq j \leq i-L}(pre_j)\)的候选集合。因此我们不需要枚举\(j\),只需要用一个变量记录当前最小值,每次与新取值\(pre_{i-L}取\)min$就可以了。
求出最大子段和后,就可以确定二分的上下限如何变化了。
\(Code\)
#include<iostream>
#include<cstdio>
#define sco 100010
#define eps 1e-6
using namespace std;
int n,L;
double a[sco],b[sco],pre[sco];
bool check(double num){
for(int i=1;i<=n;++i){b[i]=a[i]-num;}
for(int i=1;i<=n;++i)pre[i]=pre[i-1]+b[i];
double ans=-1e10,minv=1e10;
for(int i=L;i<=n;++i){
minv=min(minv,pre[i-L]);
ans=max(ans,pre[i]-minv);
}
return ans>=0;
}
int main(){
scanf("%d%d",&n,&L);
for(int i=1;i<=n;++i){
scanf("%lf",a+i);
}
double l=0,r=2e3,mid;
while(l+eps<r){
mid=(l+r)/2;
if(check(mid)) l=mid;else r=mid;
}
printf("%d\n",int(r*1000));
return 0;
}

浙公网安备 33010602011771号