【SJTUOJ笔记】P1085 绿色通道

https://acm.sjtu.edu.cn/OnlineJudge/problem/1085
读题读到一半以为是背包,读完之后发现果然没有这么简单……
第一思路肯定是要dp,但由于有个 t109 t ≤ 10 9 在这里放着,一切时间或空间复杂度 O(nt) O ( n t ) 的类背包写法都是行不通的。再思考一番,发现check某个长度是否合理的时间复杂度是 O(n2) O ( n 2 ) 。这样决定了大体框架:二分答案,再用dp检查答案是否合理。由于答案最大不会超过 n n (这时一道题都不做),最小当然也不会小于 0 0 (全部都做),二分对时间复杂度的贡献是 O(logn) O ( log ⁡ n ) ,重点在于如何用dp检查。
假设我们要检查的答案是 lim l i m ,容易想到一个 O(n2) O ( n 2 ) 的做法:设 f[i] f [ i ] 表示做第 i i 题、且从 1 1 i i 最大空题长度不超过 lim l i m 所需要的最小时间,利用状态转移方程

f[i]=minilim1ji{f[j]}+a[i] f [ i ] = min i − l i m − 1 ≤ j ≤ i { f [ j ] } + a [ i ]

枚举 i i j j 即可。总时间复杂度 O(n2logn) O ( n 2 log ⁡ n ) ,只能过 40% 40 % 的数据。对于 100% 100 % 的数据,估计要找一个 O(nlogn) O ( n log ⁡ n ) 的算法才行,这就需要把dp优化到 O(n) O ( n ) 级别。
dp过程中,我们需要维护的是长度固定为 lim+1 l i m + 1 的区间 [ilim1,i] [ i − l i m − 1 , i ] 上的最小值,是一个很经典的单调队列模型。因此很自然地想到用单调队列进行优化,使dp变为 O(n) O ( n )
核心部分如下:

bool check(int lim){
    memset(f, 0, sizeof(f));
    memset(q, 0, sizeof(q)); //q是单调队列,类型为pair<int, int>,分别代表元素的位置和数值
    int front = 0, rear = 0;
    q[rear++] = make_pair(0, 0); //初始化队列,添加元素(0, 0)
    for (int i = 1; i <= n; ++i){
        while (front != rear && q[front].first < i - lim - 1) //把超过区间范围的元素出队
            ++front;
        f[i] = q[front].second + a[i];
        while (front != rear && f[i] <= q[rear - 1].second) //把之后肯定不会有用的元素出队
            --rear;
        q[rear++] = make_pair(i, f[i]); //当前元素入队
    }
    //检查时注意不能直接看f[n],因为f[i]表示的是必定做第i道题,但最后n-lim到n这几道题也可以空下来不做。因此,只要f[n-lim]到f[n]中有一项不超过t,这个lim就是成立的。
    for (int i = n - lim; i <= n; ++i) 
        if (f[i] <= t)
            return true;
    return false;
}

你可能感兴趣的:(算法与数据结构)