Loading

单调队列

写法

首先要有一个双端队列:

struct My_dequeue{
    int hh = 1, tt = 0, q[N];
    void clear(){hh = 1; tt = 0;}
    void push_front(int k){q[-- hh] = k;}
    void push_back(int k){q[++ tt] = k;}
    void pop_front(){hh ++;}
    void pop_back(){tt --;}
    int front(){return q[hh];}
    int back(){return q[tt];}
    int head(){return hh;}
    int tail(){return tt;}
    bool empty(){return hh > tt;}
}q;

以下给出单调队列的标准写法。

int calc(int i){//返回下标对应的值
    return inp[i];
}

void insert(int x){//传入下标
    while (!q.empty() && calc(q.back()) <= calc(x)) q.pop_back();//维护最大值
    q.push_back(x);//存入下标
}

int query(int x){
    while (q.front() <= x - k) q.pop_front();//弹出不符合条件的下标
    return calc(q.front());//返回值
}

q.clear();
for (int i = 1; i <= n; i ++) {
    insert(i);
    ans[i] = query(i);
}

作用

维护左右端点单调的若干定长连续区间的 RMQ。

可以用于优化 DP 。要求当前状态的所有值可以从上一个状态的某个连续的段的值得到,将要对这个连续的段进行 RMQ 操作,且相邻状态的段的左右区间满足非降的关系。

具体的说,要求 DP 的状态转移方程满足以下形式( \(b\) 为任意与 \(k\) 无关的式子,\(x,y\) 为任意与 $j $ 无关的式子):

\[f(i,j)=\max\{f(i-1,k)\}+b,k\in[j-x,j+y] \]

\[f(i)=max\{f(k)\}+b,k\in[i-x,i-y],x>0,y>0 \]

那么,就可以通过单调队列优化其时间复杂度。

具体的说,我们可以用单调队列维护上一层的 DP 数组的区间最值。由于每一层内求最值的区间长度固定,就可以仿照上面的写法将其构造成一个单调队列,计算新状态时就可以以均摊 \(O(1)\) 的时间复杂度维护最值信息,这样总时间复杂度就可以少一个 \(n\)。是一个优秀的优化。

单调队列优化通常配合滚动数组一起适用,可以让时空均少一个 \(n\)

例如:

\[f(i)=max\{f(k)\}+a[i],k\in[i-R,i-L] \]

那么代码是这样的:

int calc(int i){//返回下标对应的值
    return f[i];
}

void insert(int x){//传入下标
    while (!q.empty() && calc(q.back()) <= calc(x)) q.pop_back();//维护最大值
    q.push_back(x);//存入下标
}

int query(int x){
    while (q.front() <= x - R - 1) q.pop_front();//弹出不符合条件的下标
    return calc(q.front());//返回值
}

q.clear();
for (int i = L; i <= n; i ++) {
    insert(i - L);
    f[i] = query(i) + a[i];
    if (i + R > n) ans = max(ans, f[i]);
}

posted @ 2023-06-04 22:29  TKXZ133  阅读(42)  评论(0)    收藏  举报