题目传送门:t102. 最佳牛围栏 - AcWing题库高质量的算法题库https://www.acwing.com/problem/content/104/
解题思路
整体解析
按照题目要求我们要找到一块连续的区域,使其里面每块地里面的平均值最大,且这块区域的长度要大于f
二分处理
“平均值可能的最大值”二分的关键词,所以考虑用二分去解决。又因为数据无序且不可排序(要找的是连续的地,排完序后再原数组不一定连续),而且平均值是小数,所以考虑用实数二分猜测平均值,check函数判断。l = 0,r = 2000(其实跟精确可以是牛最多的那块地牛的数量),实数二分边界无需判定,l = mid,r = mid即可。
check函数编写
要判断的是是否存在一块区域,里面地的平均值>=mid(即实数二分猜测的平均值)。这里有2个处理技巧:
1、为了快速确定一块区间的平均值,可以采用前缀和的方式(设s为前缀和数组),s里面存储的为1-i区间的平均值,s[i] = s[i-1]+a[i]-mid。如果平均值>=mid(在s数组中的体现就是s[i]>0)且长度>=f,说明当前区域平均值合法,return true,否则return false。
2、为了快速找到合法的值可以采用,可以尽量找平均值最大的区间。如何找呢?涉及到一些dp的思想,就是找到当前判断区间的之前区间的最小值,而当前区间-这个最小值,便可能尽量大。如果发现s[i]-min >= 0,且区域长度>=f的话,说明合法 ,否则不合法
输出处理
输出r*1000,但不要是l*1000,因为会涉及到精度问题所以l*1000会和正确答案有所偏差
代码
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6+10;
double a[N],s[N]; //a为牛数量数组,s为平均数前缀和数组
int n,f;
double l = 0,r = 0,mid;
bool check(double x){
for(int i = 1; i <= n; i++){
s[i] = s[i-1]+a[i]-x; //求平均值前缀和数组
}
double minn = 0; //0~i-1区间的最小区间
for(int i = f; i <= n; i++){
minn = min(minn,s[i-f]); //dp思想维护minn最小值
if(s[i] >= minn) return 1; //s[i]>=minn,实际含义就是s[i]-minn >= 0,看看这个区间里的平均数是否大于等于猜测的mid,也就是判断是否合法
}
return 0;
}
int main(){
cin >> n >> f;
for(int i = 1; i <= n; i++){
scanf("%lf",&a[i]);
r = max(r,a[i]); //牛数最多的地即为r
}
while(r-l > 1e-5){ //二分实数的精度为le-5
mid = (l+r)/2;
//实数二分无边界
if(check(mid)) l = mid;
else r = mid;
}
//输出r*1000而不是l*1000,否则会涉及到精度偏差
printf("%d",int(r*1000));
return 0;
}