A.题意:给定个数为N的数列,从中挑一些不小于L的连续子段,求这些子段当中的数平均值最大是多少?
思路:二分平均值转化为判定。我们直接去求这个>=L的子段当中的最大平均值比较难求。所以我们可以用二分的方法枚举mid,然后在判定这个mid是否合法。
判定方法为,是否存在一个长度大于L的连续子段它的平均值>=mid。如果不存在,说明以mid为平均值取大了。则取r=mid。
对平均值的处理有一种特殊方法,另每一个数都减去mid,则判定方法就转化为是否存在一个长度>=L的连续子段,使得每个数的和非负(最大的都>=0,则 一定存在)。
求某一连续子段的方法,利用前缀和来求解,eg sum[ i ] - sum [ j ]就是求 j 到 i 的连续子段和
另外用双指针来求>=L的区间的最大和。因为要保证长度是大于L的 所以i要从L开始往后递增,而 j 从 i-L 开始往前递减, sum[ i ] - sum [ j ]就保证了一个大于L的区间。但是由于i 从L往后每递增一个, j 的可选择空间也就增加一个,j的初始空间只有一个选择,而这道题判定方法问存不存在,所以要求我们求连续子段的最大的和,于是我们只需要保存前 i - L 当中最小的那个前缀和就可以了。
下面上代码:
#include<iostream> #include<cstdio> #include<cmath> using namespace std; const int maxn=1e5+6; const double eps=1e-6; int n,L; double cow[maxn]; double sum[maxn]; double b[maxn]; bool check(double ave) { for(int i=1;i<=n;i++) b[i]=cow[i]-ave; sum[0]=0; for(int i=1;i<=n;i++) sum[i]=sum[i-1]+b[i]; double ans=-1e10; double min_v=0; for(int i=L;i<=n;i++) { min_v=min(min_v,sum[i-L]); ans=max(ans,sum[i]-min_v); } if(ans>=0) return true; else return false; } int main() { cin>>n>>L; for(int i=1;i<=n;i++) //scanf("%lf",&cow[i]); cin>>cow[i]; double l=0,r=2000; while(r-l>eps) { double mid=(r+l)/2; if(check(mid)) l=mid; else r=mid; } printf("%d ",(int)(r*1000)); return 0; }