题目要求出最长的空题段最短的长度,显然可以二分答案。
考虑如何 check。
设二分到的值是 (x),即最长的空题段长度至少为 (x)。
其实整个 check 的过程可以看作一个 DP,设 (dp_i) 表示 (1sim i) 中 写第 (i) 道题 且 最长的空题段长度 (leq x) 所需的最短时间。
由于空题段最长为 (x),且区间 ([i-x, i - 1]) 中有 (x) 个点, 所以转移方程不难得出:(dp_i=min_{i-x-1leq j leq i-1}{dp_j} + a_i)。
问题又变成了一个典型的滑动窗口问题,可以使用单调队列优化。
#include <bits/stdc++.h>
using namespace std;
const int N = 50003;
int n, m, t, a[N], dp[N], q[N], hh, tt, ans;
inline bool check(int x)
{
memset(dp, 0, sizeof dp);
hh = tt = 0; //队列中预先存储了一个 dp[0] = 0
for (int i = 1; i <= n; i+=1)
{
while (hh <= tt && q[hh] < i - x - 1) ++hh; //队头超出了范围
dp[i] = dp[q[hh]] + a[i]; //转移
while (hh <= tt && dp[q[tt]] >= dp[i]) --tt; //维护队列单调性
q[++tt] = i; //加入队列
}
int sum = 2000000007;
for (int i = n - x; i <= n; i+=1)
sum = min(sum, dp[i]); //求出最短的时间
return sum <= t; //最多只有 t 分钟
}
int main()
{
cin >> n >> t;
for (int i = 1; i <= n; i+=1) cin >> a[i];
int l = 0, r = n;
while (l <= r)
{
int mid = (l + r) >> 1;
if (check(mid)) ans = mid, r = mid - 1;
else l = mid + 1;
}
cout << ans << endl;
return 0;
}