题目描述
高二数学《绿色通道》总共有 nnn 道题目要抄,编号 1,2,…,n1,2,…,n1,2,…,n,抄第iii 题要花 aia_iai 分钟。
小 Y 决定只用不超过ttt 分钟抄这个,因此必然有空着的题。
每道题要么不写,要么抄完,不能写一半。
下标连续的一些空题称为一个空题段,它的长度就是所包含的题目数。
这样应付自然会引起马老师的愤怒,最长的空题段越长,马老师越生气。
现在,小 Y 想知道他在这 ttt 分钟内写哪些题,才能够尽量减轻马老师的怒火。
由于小 Y 很聪明,你只要告诉他最长的空题段至少有多长就可以了,不需输出方案。
输入格式
第一行为两个整数 n,tn,tn,t。
第二行为 nnn 个整数,依次为 a1,a2,…,ana_1,a_2,…,a_na1,a2,…,an。
输出格式
输出一个整数,表示最长的空题段至少有多长。
数据范围
0<n≤5×1040<n≤5×10^40<n≤5×104,
0<ai≤30000<a_i≤30000<ai≤3000,
0<t≤1080<t≤10^80<t≤108
输入样例
17 11
6 4 5 2 5 3 4 5 2 3 4 5 2 3 6 3 5
输出样例
3
算法思想
二分搜索
分析题目描述,求最长的空题段至少有多长,才能保证在ttt分钟内抄完题目。求至少有多长,可以考虑使用二分搜索进行查找。假设最长的空题段的长度至少为xxx满足要求,那么xxx一定在区间[0,n][0,n][0,n]中,如下图所示:
- 区间的左边,即空题段的长度小于xxx,此时要花更多的时间,因此不满足要求
- 区间的右边,即空题段的长度大于等于xxx,此时花更少的时间就可以满足要求
因此本题符合二分的性质,可以使用二分搜索来确定xxx的值。
DP+单调队列优化
二分之后,需要确定在最长空题段长度不超过xxx的情况下,需要花费的最少时间,可以使用DP求解。
状态表示
f[i]
表示完成前i
题在满足情况的条件下,且抄第i
题所花费的最小总时间。
状态计算
因为最长只能有xxx个连续的空题,那么可以根据前面xxx个题的状态计算出f[i]
,也就是说能转移到f[i]f[i]f[i]的合法状态的区间为[i−x−1,i−1][i - x - 1, i - 1][i−x−1,i−1],因此:
f[i]=min{f[j]}+a[i]f[i]=\min\{f[j]\} + a[i]f[i]=min{f[j]}+a[i],i−x−1≤j≤i−1i-x-1\le j\le i-1i−x−1≤j≤i−1。
其中min{f[j]}\min\{f[j]\}min{f[j]}就是求滑动窗口[i−x−1,i−1][i - x - 1, i - 1][i−x−1,i−1]的最小值,因此可以使用单调队列进行优化。
时间复杂度
二分搜索的时间复杂度为O(logn)O(logn)O(logn),DP时间复杂度为O(n)O(n)O(n),总的时间复杂度为O(nlogn)O(nlogn)O(nlogn)。
代码实现
#include <iostream>
using namespace std;
const int N = 50010;
int n, t;
int a[N], q[N];
int f[N];
bool check(int x)
{
f[0] = 0; //初始状态
int hh = 0, tt = 0;
q[0] = 0;
for(int i = 1; i <= n; i ++)
{
//处理滑出窗口的元素
//这里要求最长只能有x个连续的空格,因此需要在[i - x - 1, i - 1]中找最小值。
if(hh <= tt && q[hh] + x + 1 < i) hh ++;
//从区间[i - x - 1, i - 1]的最小值转移到f[i]
f[i] = f[q[hh]] + a[i];
while(hh <= tt && f[q[tt]] >= f[i]) tt --;
q[++ tt] = i;
}
//打擂台求最后一段的最小值
int res = 1e9;
for(int i = n - x; i <= n; i ++) res = min(res, f[i]);
return res <= t;
}
int main()
{
cin >> n >> t;
for(int i = 1; i <= n; i ++) cin >> a[i];
//二分搜索求最长的空题段至少有多长
int l = 0, r = n;
while(l < r)
{
int mid = l + r >> 1;
if(check(mid)) r = mid;
else l = mid + 1;
}
cout << r << endl;
return 0;
}