1. 问题描述:
农夫约翰的农场由 N 块田地组成,每块地里都有一定数量的牛,其数量不会少于 1 头,也不会超过 2000 头。约翰希望用围栏将一部分连续的田地围起来,并使得围起来的区域内每块地包含的牛的数量的平均值达到最大。围起区域内至少需要包含 F 块地,其中 F 会在输入中给出。在给定条件下,计算围起区域内每块地包含的牛的数量的平均值可能的最大值是多少。
输入格式
第一行输入整数 N 和 F,数据间用空格隔开。接下来 N 行,每行输入一个整数,第 i+1 行输入的整数代表第 i 片区域内包含的牛的数目。
输出格式
输出一个整数,表示平均值的最大值乘以 1000 再向下取整之后得到的结果。
数据范围
1 ≤ N ≤ 100000
1 ≤ F ≤ N
输入样例:
10 6
6
4
2
10
3
8
5
9
4
1
输出样例:
6500
来源:https://www.acwing.com/problem/content/104/
2. 思路分析:
分析题目可以知道直接做不好做,当我们已知一个平均值avg的时候,判断是否存在一个方案使得平均值是大于等于avg,这个时候就比较好做了,我们可以尝试使用使用二分查找来解决(二分的时候多一个平均值的条件),这样我们可以枚举所有的avg,判断是否满足条件即可,最小的avg = 0,最大的avg = max(a[i]),i = 1...n,并且可以验证可以使用二分来解决的,当前的mid= (l + r) / 2,如果发现当前的mid大于等于当前的avg,说明答案是在mid的右边,否则答案在mid的左边。那么我们如何在二分的时候判断当前的avg是否满足条件呢?也即判断[1,n]的区间中是否存在连续的一段使得平均值是大于等于avg的,我们可以先使a[i] - avg,然后判断是否在[1,n]中存在连续的一段使得区间和是大于等于0的,类似于dp的枚举方式我们可以将所有的区间根据右端点是a[1],a[2],...a[n]进行分类,其中a[k]表示以当前的a[k]结尾的所有区间,包括a[1:k],a[2:k],a[3:k]...a[k:k],求解区间中一段连续的和可以预处理出前缀和,然后求解出每一类中的最大值那么就可以得到最终的答案:
由题目可以知道是整数的二分所以我们在二分的时候满足r > l,当判断当前满足条件的时候令l = mid或者是r = mid,由于存在误差所以需要最终的答案是r * 1000而不是l * 1000,乘以1000之后l与r的误差就会变大那么取整之后两个答案就有一定差距了。
3. 代码如下:
from typing import List
class Solution:
def check(self, avg: float, F: int, a: List[float]):
s = [0.0]
# 注意a的长度已经是包括0之后的长度了
n = len(a)
for i in range(1, n):
s.append(s[-1] + a[i] - avg)
mins = 0
for k in range(F, n):
mins = min(mins, s[k - F])
if s[k] >= mins: return True
return False
def process(self):
n, F = map(int, input().split())
# a前面加上一个0可以使得下标可以从1开始
a = [0]
l = r = 0
for i in range(n):
a.append(int(input()))
r = max(r, a[-1])
# 对于实数的二分如果保留三位数字那么多取两位, 并且保证r > l
while r - l > 10 ** -5:
mid = (l + r) / 2
if self.check(mid, F, a):
l = mid
else:
r = mid
# 注意在需要使用r而不是l, 因为l与r是有一定差距的并且r > l, 所以扩大1000倍之后那么取整之后就有一定的差距了
return int(r * 1000)
if __name__ == "__main__":
print(Solution().process())