题意
Farmer John's farm consists of a long row of N (1 <= N <= 100,000)fields. Each field contains a certain number of cows, 1 <= ncows <= 2000.
FJ wants to build a fence around a contiguous group of these fields in order to maximize the average number of cows per field within that block. The block must contain at least F (1 <= F <= N) fields, where F given as input.
Calculate the fence placement that maximizes the average, given the constraint.
大意是说,给你一个正整数序列,找出一个区间使得平均值最大,要求该区间的长度大于等于F。
方法
做法来源于先辈,再在此特别感谢
据说有类似凸包优化,学会后再写一遍吧
注意到(1 <= N <= 100,000),因此传统方法(暴力)就算上了前缀和,时间复杂度也是平方级别的,必然超时。
方法是:先二分答案。接着对于每个候选答案,尽量在O(n)时间内验证。
那么怎么尽快验证答案呢?
想象一个情景:一个固定左端起点长度为F的区间向右生长,直至平均值对大。循环n次来更换不同起点,所有情况都能考虑周全。
这里需要优化。首先,知道平均值要知道总和,长度为F时的和可以用前缀和一步得到。
向右延长这步也可以类似去做。我们要首先把所有整数都减去当前的候选答案。这一步很关键,因为我们要获取的是 平均值的最大,减去后,我们便可以直接使用确定起点向右的数字的和作为状态,而不用再考虑除掉个数来计算对平均值的影响。
定义t[i]为以i为起点,向右延长的最大和。如果t[i+1]>=0,那么t[i]=t[i+1]+w[i]。若t[i+1]<0,那么不如扔掉i+1及其之后的数字,故此时t[i]=w[i]。
这样,能保证O(n)时间内验证候选答案。
代码
#include<iostream>
#include<cstdio>
#include<string.h>
using namespace std;
const int MAXN=100000+5;
int N,F;
double num[MAXN];
double sum[MAXN];
double rmaxsum[MAXN];
double l=9999999,r;
inline bool qualify(double tans){
for(int i=N;i>=1;i--)
rmaxsum[i]=max(num[i]-tans,rmaxsum[i+1]+num[i]-tans);
for(int i=1;i<=N-F+1;i++){
// cout<<sum[i+F-1]-sum[i-1]<<" "<<F*tans<<" "<<tans<<endl;
if(sum[i+F-1]-sum[i-1]>=F*tans)
return true;
// cout<<"sec:"<<sum[i+F-1]-sum[i-1]-F*tans+rmaxsum[i+F]<<endl;
if(sum[i+F-1]-sum[i-1]-F*tans+rmaxsum[i+F]>=0)
return true;
}
return false;
}
int main(){
cin>>N>>F;
for(int i=1;i<=N;i++){
scanf("%lf",&num[i]);
sum[i]=sum[i-1]+num[i];
r=max(r,num[i]);
l=min(l,num[i]);
}
while(l<r-0.0001){
double mid=(r+l)/2;
if(qualify(mid))
l=mid;
else
r=mid;
}
cout<<int(r*1000)<<endl;
return 0;
}