利用双栈维护双端插入删除

今天的联考T2用到了这个trick!

不得不说的是,这两次考试都能发现某的题目利用的trick/性质都有同学在网上看过,所以多看博客有助于考试(?),特别是matrix67的博客。

经典例题是这道,如果是bz的同学可以看看这篇帖子

简单的来说,就是你需要维护一个双端队列,并且维护的信息不具有区间可减性(但是满足结合律),然后需要对整个双端队列进行区间查询。

这个时候大部分人会选择用线段树,但是出题人如果将数据范围开到了只能放 $\mathcal{O}(n)$ 做法过的情况下,就需要用到这个非常巧妙的trick了。

这个trick是均摊 $\mathcal{O}(n)$ 的。

这个trick的主要思路是:

  1. 开两个栈,栈底对栈底拼在一起正好可以组成一个双端队列。
  2. 两个栈分别维护从栈底到栈顶的「前缀」信息,这个前缀其实是指栈底到当前位置的一段连续信息。
  3. 查询的时候把两个栈拼在一起就可以快速地查询了。
  4. 弹出的时候从对应的栈顶弹出就好了。

注意到这里还没完,还有一个最重要最巧妙的步骤,也是保证了能够均摊 $\mathcal{O}(n)$ 的关键。

如果某一刻一个栈被清空了,那么就将另一个栈重新划分成两个大小相等(或相差一)的栈再从新分配。

或者也可以理解为把另一个栈的元素全部取出然后分成两半分配给两个栈。

具体证明的话就是利用势能分析:令势能函数 $\varphi = \left|size_{l} - size_{r}\right|$(就是两栈大小之差),单次插入删除最多使 $\varphi$ 增加 $1$,重新分配就是将 $\varphi$ 置 $0$,所以均摊下来是线性。

这里给一份上文的例题的代码,有点丑陋,不过除了计算答案部分应该都能看懂。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll m, p, l, r, mid, ans, cnt, front, back, q[505], w[50005], v[50005], val[100005];
string op;
struct sta {
    ll top, w[50005], v[50005], dp[50005][505];
    sta(ll Top = 0): top(Top) {
        memset(dp, -0x3f, sizeof(dp));
        dp[0][0] = 0;
    }
    void push(ll x, ll y) {
        ++top;
        w[top] = x, v[top] = y;
        memset(dp[top], -0x3f, sizeof(dp[top]));
        for(int i = 0; i < p; ++i) {
            if(dp[top - 1][i] < 0) continue;
            dp[top][i] = max(dp[top][i], dp[top - 1][i]);
            dp[top][(i + w[top]) % p] = max(dp[top][(i + w[top]) % p], dp[top - 1][i] + v[top]);
        }
    }
    void pop() {--top;}
    bool empty() {return !top;}
} sl, sr;
void rebuild(bool lv = false, bool rv = false) {
    cnt = 0;
    for(int i = sl.top; i >= 1; --i) {
        ++cnt;
        w[cnt] = sl.w[i], v[cnt] = sl.v[i];
    }
    for(int i = 1; i <= sr.top; ++i) {
        ++cnt;
        w[cnt] = sr.w[i], v[cnt] = sr.v[i];
    }
    sl.top = sr.top = 0;
    l = 1, r = cnt;
    if(lv) ++l;
    if(rv) --r;
    mid = (l + r) >> 1;
    for(int i = mid; i >= l; --i) sl.push(w[i], v[i]);
    for(int i = mid + 1; i <= r; ++i) sr.push(w[i], v[i]);
    if(lv) sl.push(w[l - 1], v[l - 1]);
    if(rv) sr.push(w[r + 1], v[r + 1]);
}
int main() {
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);
    cin >> (*new int) >> m >> p;
    while(m--) {
        cin >> op;
        if(op == "IF") {
            cin >> l >> r;
            sl.push(l, r);
        }
        else if(op == "IG") {
            cin >> l >> r;
            sr.push(l, r);
        }
        else if(op == "DF") {
            if(sl.empty()) rebuild(true, false);
            sl.pop();
            if(sl.empty()) rebuild();
        }
        else if(op == "DG") {
            if(sr.empty()) rebuild(false, true);
            sr.pop();
            if(sr.empty()) rebuild();
        }
        else if(op == "QU") {
            cin >> l >> r;
            ans = -0x3f3f3f3f3f3f3f3f;
            front = 0, back = -1;
            memcpy(val, sl.dp[sl.top], sizeof(sl.dp[sl.top]));
            for(int i = 0; i < p; ++i) val[i + p] = val[i];
            r += p, l += p;
            for(int i = r; i > l; --i) {
                while(front <= back && val[i] >= val[q[back]]) --back;
                q[++back] = i;
            }
            for(int i = 0; i < p; ++i) {
                while(front <= back && val[l] >= val[q[back]]) --back;
                q[++back] = l;
                while(front <= back && (q[front] < l || q[front] > r)) ++front;
                ans = max(ans, val[q[front]] + sr.dp[sr.top][i]);
                --l, --r;
            }
            cout << (ans < 0 ? -1 : ans) << '\n';
        }
    }
    return 0;
}
posted @   A_box_of_yogurt  阅读(24)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现
Document
点击右上角即可分享
微信分享提示