POJ 2018 Best Cow Fences(二分答案)
题目链接:http://poj.org/problem?id=2018
题目给了一些农场,每个农场有一定数量的奶牛,农场依次排列,问选择至少连续排列F个农场的序列,使这些农场的奶牛平均数量最大,求最大数量*1000/农场的个数。
思路:题目是求是否存在一个长度不小于F的子段,使得平均数最大。
1.用二分法从给定数据的最小平均数到最大平均数进行二分枚举,每次枚举的平均值为mid,那么子段的每一个元素减去mid值再求和应当大于0,否则不满足题意,这样可以不断地二分,最终找到最大的mid值
2.判断子段和可以利用前缀和,每次二分的时候,先行让每个元素减去mid值,再求一个前缀和sum[i],前缀和大于0则说明平均数大于mid
3.从 i = F开始枚举到i = N,每次都要记录当前0到(i - N)的最小前缀和minval,因为当sum[ i ] - minval时,才可能存在长度不小于F且子段和最大的序列。
4.因为是在实数域上的二分,所以要注意精度问题,精度eps一般取1e-(k+2),此题k为3(1000是10的3次方)
AC代码:
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<vector>
using namespace std;
double a[100002],b[100002],sum[100002];
int main(){
int N,F;
scanf("%d%d",&N,&F);
for(int i = 1;i<=N;i++){
scanf("%lf",&a[i]);
}
double l = -1e6, r = 1e6;
while(l+1e-5 < r){//注意精度问题
double mid = (l + r)/2;
for(int i = 1;i<=N;i++){
b[i] = a[i] - mid;//依次减去枚举的平均值mid
}
for(int i = 1;i<=N;i++){
sum[i] = (b[i] + sum[i-1]);//求前缀和
}
double ans = -1e10,minVal = 1e10;
for(int i = F;i<=N;i++){
minVal = min(minVal,sum[i-F]);//记录当前最小前缀和
//且保证子段大于等于F
ans = max(ans,sum[i] - minVal); //求子段最大和
}
if(ans >= 0){
l = mid ;
}
else{
r = mid ;
}
}
cout<<int(r*1000);
return 0;
}