单栈模拟队列小记

每次写这个都回忆不起来要去粘贴板子,之前看过的题解也普遍不太清晰,于是我来写一篇吧。

回顾双栈模拟队列,我们维护了用一头一尾两个栈拼起来表示了队列,于是现在考虑把两个栈归并起来压缩进一个栈。

弹入队尾操作就正常入栈顶即可,只需考虑如何弹出队首:

不妨用 \(s\) 代指队首栈的大小,当弹出时检查发现 \(s=0\) 则将队尾栈弹出一一加入队首栈,这个根据双栈模拟队列的复杂度分析容易知道是均摊 \(O(1)\)的。

接下来保证了 \(s>0\),我们需要找到队首栈的栈顶,不妨进行二进制分组,保证队首栈自顶向下第 \([r+1,r+2^i]\) 个元素在新栈中连续(\(s=2^i(1+2k)+r, \;i,r,k\in\mathbb{N},\; 0\leq r<2^i\)),每次弹出时我们把队首栈顶 \([1,2^{\text{v}_2\left(s\right)}]\) 个移到新栈的栈顶(显然他们此时就应当是连续的,该操作后也不会破环其他应有的连续,也只需要移动原有新栈栈顶的一段队尾栈元素),然后进行弹出,这样对队首栈的总移动次数是 \(O(n\log n)\) 的,每个队尾栈元素也只会被移动 \(O(\log n)\) 次,因为一个队尾栈元素的两次移动时刻对比必有 \(\text{v}_2(s)\) 至少增加 \(1\)

//雨停酱可爱捏~ 最喜欢雨停姐姐了!!!
#include"subway.h"
int m,r,cnt,flag[200000],stk[200000],top,t1[200001],tp1,t2[200001],tp2;
void init(int N,int M,int lim){m=M,r=1;}
int solve(int l){
    for(;r<=m&&check(r);++r)flag[top]=0,merge(stk[top++]=r);
    if(!cnt){
        for(;top;t1[tp1++]=stk[--top],undo());
        for(;top<tp1;flag[top++]=1)merge(stk[top]=t1[top]);
        cnt=tp1,tp1=0;
    }
    for(;!flag[top-1];t1[tp1++]=stk[--top],undo());
    for(;tp2<(cnt&-cnt);t2[tp2++]=stk[--top],undo());
    for(;tp1;merge(stk[top++]=t1[--tp1]))flag[top]=0;
    for(;tp2>1;merge(stk[top++]=t2[--tp2]))flag[top]=1;
    tp2=0,--cnt;
    return r-1;
}
posted @ 2025-11-21 14:45  山田リョウ  阅读(0)  评论(0)    收藏  举报