二分答案:判定平均数不小于 x 的长度 ≥ L 子段是否存在,转化为前缀和减 x 后的滑动最小值判定,O(n log V)。
OJ: roj
题目 ID: 1434
难度:普及+/提高-
标签:二分答案前缀和滑动窗口
创建: 2026-09-30 10:07
更新: 2026-10-07 11:01
形式化题目
给定长度为
样例验证连续性:2 10 3 8 5 9 的平均数为 10 3 8 5 9 4 的平均数恰为
正解
思路
朴素做法枚举所有
第一步:二分答案。 注意到单调性:如果存在长度不小于
是否存在长度
的子段,其平均数不小于 ?
第二步:减法判定。 子段
令
第三步:滑动最小值。 固定右端 best 维护历史最小即可,单次判定
整数化。 先把所有数放大
下表演示样例在
| 右端 |
窗口 |
可行? | |
|---|---|---|---|
| 6 | 否 | ||
| 7 | 否 | ||
| 8 | 否 | ||
| 9 | 否 | ||
| 10 | 是(取 10 3 8 5 9 4,差为 |
表中
代码
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-07-05 22:10
# update_at: 2026-07-05 22:10
import sys
from itertools import accumulate
def feasible(scaled: list[int], x: int, L: int) -> bool:
"""判断是否存在长度 ≥ L 的子段,其平均值 ×1000 ≥ x。
等价于:存在 i ≥ j + L 使 sum(scaled[j+1..i]) - x*(i-j) ≥ 0。
令 b[k] = scaled[k] - x,只要 i-L 前缀位置的最小 b 前缀和 ≤ 当前前缀和。
"""
prefix = list(accumulate((v - x for v in scaled), initial=0)) # b 的前缀和
best = 0 # 前 i-L 个前缀和的最小值(下标 0..i-L)
for i in range(L, len(scaled) + 1):
if prefix[i - L] < best:
best = prefix[i - L] # 滑动更新可用起点的最小前缀和
if prefix[i] - best >= 0: # 找到一个平均值不小于 x 的子段
return True
return False
def solve() -> None:
data = map(int, sys.stdin.buffer.read().split())
n, L = next(data), next(data)
scaled = [next(data) * 1000 for _ in range(n)] # 先放大 1000 倍,二分全程用整数
# 二分最大的 x,使存在长度 ≥ L 的子段平均值 ×1000 ≥ x;答案就是最大 x。
lo, hi = 0, max(scaled)
while lo < hi:
mid = (lo + hi + 1) // 2
if feasible(scaled, mid, L):
lo = mid
else:
hi = mid - 1
print(lo)
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-05 00:48
* update_at: 2026-10-05 00:48
*/
#include <cstdio>
typedef long long ll;
const ll MAXN = 100005;
ll n, L; // 序列长度、子段最短长度
ll a[MAXN]; // a[i]:第 i 个数放大 1000 倍后的值
ll prefix[MAXN]; // prefix[i]:b[1..i] 的前缀和,其中 b[k] = a[k] - x
// 判断:是否存在长度 >= L 的子段,其平均数 * 1000 >= x。
// 子段 [j+1, i] 平均数 >= x 等价于 sum(b[j+1..i]) >= 0,
// 即 prefix[i] >= prefix[j];只需 prefix[i] 不小于 j in [0, i-L] 的最小前缀和。
bool check(ll x) {
prefix[0] = 0;
for (ll i = 1; i <= n; ++i)
prefix[i] = prefix[i - 1] + a[i] - x; // 现场累加 b 的前缀和
ll best = 0; // 窗口 [0, i-L] 内 prefix[j] 的最小值,随 i 右移单调维护
for (ll i = L; i <= n; ++i) {
if (prefix[i - L] < best)
best = prefix[i - L]; // 新进入窗口的位置
if (prefix[i] >= best) // 找到平均数不小于 x 的子段
return true;
}
return false;
}
int main() {
scanf("%lld %lld", &n, &L);
for (ll i = 1; i <= n; ++i) {
scanf("%lld", &a[i]);
a[i] *= 1000; // 整体放大 1000 倍,二分全程用整数,避免浮点误差
}
// 二分最大的 x:使存在长度 >= L 的子段平均数 * 1000 >= x
ll lo = 0, hi = (ll)1e9; // 值域上界:每个数 <= 1e6,放大 1000 倍后 <= 1e9
while (lo < hi) {
ll mid = (lo + hi + 1) / 2; // 偏向中点,保证 lo 能推进
if (check(mid))
lo = mid;
else
hi = mid - 1;
}
printf("%lld\n", lo); // 最大可行 x 即答案的 1000 倍(下取整)
return 0;
}复杂度
- 时间:每次判定
,二分 轮,总计 , 时约 次基本运算。 - 空间:
(前缀和数组)。
总结
本题是"二分答案 + 减法判定"的经典模型:把"最大化平均值"变成"平均值不小于