双栈维护滑动窗口的 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);
}

浙公网安备 33010602011771号