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;
}
posted @ 2021-08-08 15:43  ssl_lhj  阅读(50)  评论(0)    收藏  举报