C++题解:绿色通道——单调队列优化DP

博客围绕高二数学《绿色通道》抄题问题展开,小Y要在不超t分钟内抄题,需减轻马老师怒火。采用二分搜索确定最长空题段至少长度x,再用DP+单调队列优化计算在最长空题段长度不超x时的最少花费时间,总时间复杂度为O(nlogn)。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目描述

高二数学《绿色通道》总共有 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<n5×104,
0<ai≤30000<a_i≤30000<ai3000,
0<t≤1080<t≤10^80<t108

输入样例

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][ix1,i1],因此:

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-1ix1ji1

其中min⁡{f[j]}\min\{f[j]\}min{f[j]}就是求滑动窗口[i−x−1,i−1][i - x - 1, i - 1][ix1,i1]的最小值,因此可以使用单调队列进行优化。

时间复杂度

二分搜索的时间复杂度为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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

少儿编程乔老师

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值