二分平均值转换为前缀和最值判定,结合双指针前缀维护在 O(n log(max_a / eps)) 内求解最大平均值子段。
OJ: roj
题目 ID: 3012
难度:提高
标签:二分前缀和双指针
创建: 2026-10-01 09:56
更新: 2026-10-07 13:50
形式化题目
给定一个长度为
求一个长度不小于
达到最大。最终输出该最大平均值乘以
正解
思路
直接枚举所有长度
注意到“平均值能否达到
1. 判定条件的转化
对于给定的实数
将不等式变形:
令
是否存在
2. 线性扫描判定
固定右端点
为了使
当
这样,单次判定只需
3. 算法可视化
以样例
flowchart LR
subgraph Scan[右端点扫描 r 从 F 到 N]
direction TB
subgraph Candidate[合法起点前缀和池]
direction LR
P0["S[0]"] --- P1["S[1]"] --- Pk["..."] --- Pf["S[r-F]"]
end
Update["min_l = min(min_l, S[r-F])"]
Check{"S[r] >= min_l ?"}
Candidate --> Update --> Check
end
在二分范围
代码
Python 版:
#!/usr/bin/env python3
# Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
# rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
# rainboy的学习导航网站: https://idx.roj.ac.cn
# create_at: 2026-03-31 09:00
# update_at: 2026-03-31 09:00
import sys
def has_valid_fence(prefix_sums: list[float], f: int) -> bool:
"""检查是否存在长度至少为 f 且总和非负的连续子数组。
维护扫到当前位置右端点 r 时,所有合法左端点 l (0 <= l <= r - f) 对应的
最小前缀和 min_l。若 prefix_sums[r] - min_l >= 0,说明存在满足条件的区间。
"""
min_l = 0.0
for r in range(f, len(prefix_sums)):
l = r - f
if prefix_sums[l] < min_l:
min_l = prefix_sums[l]
if prefix_sums[r] >= min_l:
return True
return False
def check(a: list[int], f: int, mid: float) -> bool:
"""判断平均值能否达到 mid。"""
prefix_sums = [0.0]
total = 0.0
for x in a:
total += x - mid
prefix_sums.append(total)
return has_valid_fence(prefix_sums, f)
def max_average_fence(a: list[int], f: int) -> int:
"""二分平均值求长度至少为 f 的最大连续平均值,结果乘 1000 向下取整。"""
left = 0.0
right = 2000.0
while right - left > 1e-5:
mid = (left + right) / 2.0
if check(a, f, mid):
left = mid
else:
right = mid
return int(right * 1000)
def solve() -> None:
data = iter(sys.stdin.buffer.read().split())
try:
first = next(data)
except StopIteration:
return
n = int(first)
f = int(next(data))
a = [int(next(data)) for _ in range(n)]
ans = max_average_fence(a, f)
print(ans)
if __name__ == "__main__":
solve()C++ 版(同一算法):
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-10-06 13:54
* update_at: 2026-10-06 13:54
*/
#include <iostream>
using namespace std;
const int MAXN = 100005;
typedef long long ll;
ll n, f;
ll a[MAXN]; // a[i] 表示第 i 块地里的牛的数量
double sum[MAXN]; // sum[i] 表示前 i 项的 (a[i] - mid) 前缀和,随二分基准 mid 变化
// 判断平均值能否达到 mid,即是否存在长度不小于 f 且总和非负的连续子段。
bool check(double mid) {
sum[0] = 0.0;
for (int i = 1; i <= n; i++) {
sum[i] = sum[i - 1] + a[i] - mid;
}
// 固定右端点 r,合法左端点前缀下标 p = l - 1 满足 0 <= p <= r - f。
// min_sum 维护这些 p 中 sum[p] 的最小值,若 sum[r] >= min_sum 则说明存在合法子段。
double min_sum = 0.0;
for (int r = f; r <= n; r++) {
int p = r - f;
if (sum[p] < min_sum) {
min_sum = sum[p];
}
if (sum[r] >= min_sum) {
return true;
}
}
return false;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> f;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
// 二分最大平均值:平均值可达 mid 具有单调性,左侧可行则抬高下界。
double left = 0.0;
double right = 2000.0;
while (right - left > 1e-5) {
double mid = (left + right) / 2.0;
if (check(mid)) {
left = mid;
} else {
right = mid;
}
}
// right 始终略大于真实最大值,乘 1000 后向下取整即为答案。
ll ans = (ll)(right * 1000);
cout << ans << "\n";
return 0;
}复杂度
- 时间复杂度:单次判定遍历序列一次,耗时
。二分区间长度为 ,精度阈值为 ,二分迭代次数约为 次。总时间复杂度为 ,其中 。在 时,总操作量约 ,耗时远低于 1 秒。 - 空间复杂度:存储输入数组和前缀和数组需要
额外空间。
总结
解决“最大平均值连续子段”问题的核心是将平均值减去基准量