二分最长空题段长度 L,用单调队列维护平移窗口的最小值,在 O(n) 内判定可行性,总复杂度 O(n log n)。
OJ: roj
题目 ID: 1603
难度:提高
标签:动态规划单调队列二分答案python一本通
创建: 2026-09-30 21:17
更新: 2026-10-07 13:50
形式化题目
给定长度为
其余下标为空题。极大的连续空题下标段称为一个空题段,其长度为包含的题目数;"从第 1 题开始连续空"与"到第
一个等价说法:所有空题段长度都不超过
样例
| 空题段 | 1–3 | 5 | 7–9 | 11–13 | 15–17 |
|---|---|---|---|---|---|
| 长度 | 3 | 1 | 3 | 3 | 3 |
最长空题段是 3;而若要求空题段
正解
思路
朴素想法与瓶颈。 枚举答案
其中
最后一道抄的题
优化:窗口只平移,最小值可以复用。 算
| 窗口 |
最优前驱 |
|||
|---|---|---|---|---|
| 1 | 6 | [0, 0] | 0 | 6 |
| 2 | 4 | [0, 1] | 0 | 4 |
| 3 | 5 | [0, 2] | 0 | 5 |
| 4 | 2 | [0, 3] | 0 | 2 |
| 5 | 5 | [1, 4] | 4 | 7 |
| 6 | 3 | [2, 5] | 4 | 5 |
| 7 | 4 | [3, 6] | 4 | 6 |
| 8 | 5 | [4, 7] | 4 | 7 |
观察两点:
最后把"枚举
| 收尾最小总时间 | 是否 |
|
|---|---|---|
| 0(全抄) | 67 | 否 |
| 1 | 27 | 否 |
| 2 | 18 | 否 |
| 3 | 11 | 是 |
solve 中 lo, hi = 0, n 的循环:feasible(mid) 为真则 hi = mid,否则 lo = mid + 1,结束时 lo 就是最小可行
代码
与正文符号的对应:feasible 的 limit 参数,cost,budget,单调队列是 window(存下标),判定 DP 整体在 feasible 中,二分框架在 solve 中。
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-09-30 21:18
# update_at: 2026-09-30 21:18
import sys
from collections import deque
def feasible(limit: int, cost: list[int], budget: int) -> bool:
"""是否存在抄题方案,使每段连续空题长度 ≤ limit 且总时间 ≤ budget。"""
n = len(cost)
if limit >= n: # 一题都不抄:整段空题长度正好是 n
return True
# dp[i]:第 i 题抄、且前 i 题的空题段都不超过 limit 时的最小总时间;dp[0] = 0 表示一题未抄。
# 第 i 题与上一道抄的题 j 之间空了 i-j-1 题,要求 i-j-1 ≤ limit,即 j ≥ i-limit-1。
dp = [0] * (n + 1)
window = deque([0]) # 单调队列存下标,队首恒为窗口 [i-limit-1, i-1] 内 dp 最小的 j
for i in range(1, n + 1):
while window[0] < i - limit - 1: # 队首滑出窗口
window.popleft()
dp[i] = cost[i - 1] + dp[window[0]]
while dp[window[-1]] >= dp[i]: # 维护队内 dp 单调递增
window.pop()
window.append(i)
# 末尾空段 n-i 也要 ≤ limit,所以最后一道抄的题 i 只需在 [n-limit, n] 里挑。
return min(dp[n - limit:]) <= budget
def solve() -> None:
data = iter(map(int, sys.stdin.buffer.read().split()))
n, budget = next(data), next(data)
cost = [next(data) for _ in range(n)]
# 空题段越长越安全(可行域对 limit 单调),二分最小可行 limit。
lo, hi = 0, n
while lo < hi:
mid = (lo + hi) // 2
if feasible(mid, cost, budget):
hi = mid
else:
lo = 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 12:07
* update_at: 2026-10-05 12:07
*/
// 二分答案 + 单调队列优化 DP。
// 判定:给定空题段上限 limit,求"任意连续 limit+1 题至少抄 1 道"方案的最小抄题总时间,
// 看它是否不超过总时间 t。可行域对 limit 单调不减,故二分最小可行 limit。
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 50005;
typedef long long ll;
int n; // 题目总数
ll budget; // 总时间上限 t
ll a[MAXN]; // a[i]:抄第 i 题要花的时间(1 开始编号)
ll dp[MAXN]; // dp[i]:第 i 题抄、且前 i 题空题段均不超过 limit 时的最小总时间
int que[MAXN]; // 单调队列,存下标 j;队内 dp 值递增,队首是窗口内 dp 最小的 j
int head, tail; // 队列的左右端点,区间 [head, tail)
// 判断空题段上限为 limit 时是否能在 budget 内抄完。
bool feasible(int limit) {
if (limit >= n) { // 一题都不抄:整段空题长度正好是 n,必可行
return true;
}
// dp[0] = 0:虚拟起点"此前一题都没抄"。
// 第 i 题与上一道抄的题 j 之间空了 i-j-1 题,要求 j >= i-limit-1,
// 窗口下界自动管住了开头空段,无需特判。
dp[0] = 0;
que[0] = 0;
head = 0;
tail = 1;
for (int i = 1; i <= n; i++) {
while (que[head] < i - limit - 1) { // 队首滑出窗口 [i-limit-1, i-1]
head++;
}
dp[i] = a[i] + dp[que[head]];
while (head < tail && dp[que[tail - 1]] >= dp[i]) { // 维护队内 dp 单调递增
tail--;
}
que[tail] = i;
tail++;
}
// 末尾空段 n-i 也要 <= limit:最后一道抄的题 i 只需在 [n-limit, n] 里挑最小 dp。
ll best = dp[n];
for (int i = n - limit; i < n; i++) {
if (dp[i] < best) {
best = dp[i];
}
}
return best <= budget;
}
void solve() {
// 空题段越长越容易可行,二分最小可行 limit,lo == hi 即答案。
int lo = 0, hi = n;
while (lo < hi) {
int mid = (lo + hi) / 2;
if (feasible(mid)) {
hi = mid;
} else {
lo = mid + 1;
}
}
cout << lo << endl;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> budget;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
solve();
return 0;
}复杂度
- 时间复杂度:
——单次判定因单调队列是 (每个下标至多入队、出队各一次),二分在 上 次判定;与标准 C++ 解法(二分答案 + 单调队列优化 DP)同阶。 时约 次基本操作。 - 空间复杂度:
, dp数组个整数与最坏 的单调队列。
总结
本题的核心是两步翻译:先把"最长空题段