绿色通道

考虑二分答案:

$f_i$ 表示完成前 $i$ 个后的最少耗时,就有转移式:

$$f[i] = \max(f[j]) + a[i], i - mid\leq j \leq i-1$$

最后检查 $f[n]$ 是否 $\leq t$ 就行了

直接做 $O(n^2)$,优化:

  1. 线段树 $O(n log_2n)$:

    • 每次求完 $f[i]$ 就在 $i$ 这个位置上加上 $f[i]$ (单点修改)。
    • 求 $f[i]$ 时从线段树中查询 $i - mid$ 到 $i - 1$ 的和(区间查询)。

    线段树板子,这个还是挺简单的。

  2. 单调队列 $O(n)$:

    • 用一个单调递增的队列记录 $i - mid$ 到 $i - 1$ 的值。
    • 求完 $f[i]$ 后,就考虑把 $i$ 插入队列:若 $f[i]$ 比队尾元素的值小,则弹出队尾元素,直到 $f[i]$ 队尾元素的值大,再从尾部插入 $i$。
    • 求 $f[i]$ 时,就考虑取对头元素:若队头元素大与于 $i - mid$,即不在区间内,弹出队头元素,直到在 $[i-mid,i-1]$ 区间内,则上式的 $j$ 就是队头,更新一下就行了。

    代码不长,乱搞就行了。

本蒟蒻写了单调队列优化,代码如下 (有点丑):

#include <cstdio>
#include <iostream>
using namespace std;

const int maxn = 5e4 + 10;
int w[maxn];
int n, t, f[maxn];
int head, tail, q[maxn];

bool check (int x) {
    f[0] = 0;
    head = 0; tail = -1;
    for (int i = 1; i <= x + 1; ++i) {
        f[i] = w[i];
        while (head <= tail && f[q[tail]] >= f[i]) tail --;
        q[++tail] = i;
    }
    for (int i = x + 2; i <= n + 1; ++i) {
        while (head <= tail && q[head] < i - x - 1) head ++;
        f[i] = f[q[head]] + w[i];
        while (head <= tail && f[q[tail]] >= f[i]) tail --;
        q[++tail] = i;
    }
    return f[n + 1] <= t;
}

int main() {
    scanf ("%d%d", &n, &t);
    for (int i = 1; i <= n; ++i) {
        scanf ("%d", &w[i]);
    }
    int l = 1, r = n;
    while (l < r) {
        int mid = (l + r) >> 1;
        if (check (mid)) r = mid;
        else l = mid + 1;
    }
    printf ("%d", l);
    return 0;
}
posted @ 2022-01-25 16:12  wangzhongyuan  阅读(8)  评论(0编辑  收藏  举报  来源