双栈维护滑动窗口的 trick

更新日志 2025/08/12:开工。

概念

使用滑动窗口维护一些容易加入但不容易删除的信息时,可以考虑使用两个栈做到 \(O(n)\) 的加入次数同时实现加入与删除操作。

思路

视两个栈栈底相邻呈对顶状,这里以右加左减为例:

栈内每个点,同时维护这个点的信息与栈底到它这个序列上,需要维护的区间信息。加入时,直接加到右端点即可。

删除时,如果左栈有元素,就直接弹出(左栈栈顶为最左边);如果没有元素,就把右边的栈内元素一一弹出加入左栈,然后再弹出左栈的一个点。

这样每个点只会插入、弹出各两次,复杂度是 \(O(n)\) 的。

左加右减同理。但显然不能同时维护两种操作。

例题

JZOJ6358:给你一个二元组 \((a_i,b_i)\) 序列,求最小的区间 \([l,r]\) 的长度,其中 \([l,r]\) 满足这里面的每个二元组选或不选,使得 \(\sum_{a_i} = w\) 且 $\sum b_i \le k。如果无解,请输出 “-1”。保证 \(1 \le s \le 10^4, 1 \le b_i \le 2 \times 10^4, 1 \le a_i \le w \le 5 \times 10^3, 1 \le k \le 10^9\)

不难发现满足条件的区间具有单调性,右边界随左边界增大单增。

有个很显然的 DP 能维护区间最小 \(\sum b\),这个 DP 不好删除,直接套用上面的 trick 就行了。

代码
const int N=1e4+5,W=5e3+5;

ll s,w,k;
ll a[N],b[N];

struct node{ll k,f[W];node(ll x){k=x;memset(f,0x3f,sizeof f);}};
stack<node> sl,sr;
inline void insert(int k,stack<node> &stk=sr){
    node nd(k);
    if(stk.empty()){
        nd.f[0]=0;nd.f[a[k]]=b[k];
        stk.push(nd);
    }else{
        nd=stk.top();nd.k=k;
        per(i,w,a[k])chmin(nd.f[i],nd.f[i-a[k]]+b[k]);
        stk.push(nd);
    }
}
inline void pop(){
    if(sl.empty())while(sr.size())insert(sr.top().k,sl),sr.pop();
    sl.pop();
}
inline ll query(){
    ll res=INF;
    if(sl.empty()&&sr.empty())return res;
    if(sl.empty())return sr.top().f[w];
    if(sr.empty())return sl.top().f[w];
    rep(i,0,w)chmin(res,sl.top().f[i]+sr.top().f[w-i]);
    return res;
}

inline void Main(){
    cin>>s>>w>>k;
    rep(i,1,s)cin>>a[i]>>b[i];
    int ans=inf;
    int r=0;
    rep(l,1,s){
        while(query()>k){
            if(r==s)return put(ans<inf?ans:-1),void();
            insert(++r);
        }
        chmin(ans,r-l+1);
        pop();
    }
    put(ans<inf?ans:-1);
}
posted @ 2025-08-12 18:41  LastKismet  阅读(16)  评论(0)    收藏  举报