动态维护单调栈的一种方法

· · 算法·理论

前言:

本人没有见过需要此算法的题目,所以文中的题目均没有来源。

感谢 xzf_200906 对本文的帮助。

一个问题:

给你一个长为 n 的序列 a

## 解法: 设区间 $[1,i]$ 形成的单调下降的栈为 $Z_i$。即求 $\max_{i=1}^{n}|Z_i|$ 和 $\sum_{i=1}^{n}|Z_i|$。 对于 $i$,考虑向 $Z_{i,|Z_i|-1}$ 连一条边,最后会连出一片森林。这不方便,不妨考虑在序列的开头插入一个 `inf`,使其变成一棵树,称其为栈树。 ![](https://cdn.luogu.com.cn/upload/image_hosting/0o5nuzw9.png) 考虑修改操作时,就是找到 $x$ 祖先节点中第一个大于 $a_x$ 的节点 $i$,再找到 $i$ 的子节点中第一个大于 $a_x$ 的节点 $j$,将 $dfn$ 序在 $(dfn_i,dfn_j)$ 的子树顺次接到 $x$ 的上,然后再将 $x$ 接到 $i$ 上。 ![](https://cdn.luogu.com.cn/upload/image_hosting/5fij7bhf.png) 维护考虑括号序,视左括号为 $+1$,右括号为 $-1$,$|Z_i|$ 为位置 $i$ 的前缀和。 这个过程中 $dfn$ 序显然不变,也就是左括号的相对位置不变,那么直接将右括号看成减一的标记,修改操作相当于移动标记,设一次操作需要左移的标记集合为 $S$。 一次移动一个标记显然可以卡到 $O(nq)$,但是我们注意到存在多个右标记在同一个节点的情况,因此考虑先求出 $|S|$,然后每次移动一个点上的所有减一标记,则一次操作的时间复杂度变为 $S$ 中的标记涵盖的点数 $\times\log n$。 ### 证明 $q$ 次中 $S$ 中的标记涵盖的点数为 $O(n+q)

考虑 (x,i) 这条链,当一个点右侧存在兄弟节点时,显然需要移动的右括号会存在于一个新的节点,统计这样的点的个数,并将其称为一次操作的代价。

那么考虑 q 次操作后代价总和的上界。将所有右侧存在兄弟节点的点的权值设为 1,其余点为 0,每次操作的代价为 (x,i) 这条链的点权和,将其称为链的代价。

将链的代价分为两部分,一种是初始树中有的,另一种是操作后产生的新代价。

发现每次操作结束后,会将链上的所有节点权值变为 0,且可能产生的新代价 \le2x 产生的代价,将一个原本为 0 的点改为 1)。

那么一次操作的代价相当于 选出初始树中的代价和新产生的代价的一部分删去,并将新产生的代价总量 +2

### 关于具体维护 对于标记建线段树,对于每个节点记录信息:左、右端点 $l/r$,最小前缀和 $mn$,前缀和最小的点中最靠右的点的点权 $v$,整段区间的权值总和 $ed$。信息容易合并。 设计以下函数: `findl(x,val)`:找到最大的 $y$ 满足 $[y,x]$ 的 $v$ 大于 $val$。 `findr(x,val)`:找到最小的 $y$ 满足 $[x,y]$ 的 $v$ 大于 $val$。 `next(x)`:找到最小的 $y$ 满足 $[x,y]$ 的 $mn$ 小于 $0$。 使用 `findl` 即可找到 $i$,进而求出 $|S|$。然后用 `next` 找出下一个需要移动标记的点进行标记移动。最后使用 `findr` 找到 $j$ 就能完成一次修改。 最值和总和在移动时顺便维护即可。 上述三个函数都可以通过线段树二分实现,总时间复杂度 $O((n+q)\log n)$。 ## 栈树与笛卡尔树的联系 考虑笛卡尔树的建树,弹栈时将最后一个弹出的点接到当前节点的左儿子。这两个点在栈树上为相邻的兄弟节点。 于是将栈树中所有右侧有兄弟节点的点 断开与其父亲节点的边,向其右侧的兄弟节点连边,就是一颗笛卡尔树。 ![](https://cdn.luogu.com.cn/upload/image_hosting/q446uv50.png) 这意味着可以用它维护动态笛卡尔树的形态。前人早已有动态笛卡尔树的相关研究,用的是 `LCT`,但是 `LCT` 难以维护子树最值问题。 于是考虑题目:修改操作不变,要求维护笛卡尔树的一个子树内的最长左、右链的长度。 解法: 容易发现一条右链的长度为它深度最大的节点上 的标记数量减一。 于是可以用 `findl` 和 `findr` 求出一个子树对应在线段树上的的区间 $[l,r]$,答案为区间内节点上 标记数量的最大值减一,注意节点 $r$ 要单独计算。 最长左链的维护只需要从前缀单调栈建树改为后缀单调栈建树即可。 最后以最长右链的维护给出一份代码(值域相同则编号较小的更大): ```cpp int n,q,a[N]; vector<int>v[N]; int d[N]; int st[N],tp; struct node{ int mn,ed,v,l,r;//most right v int mx; }; struct tree{ node tr[N*4]; node bin(node a,node b){ node k; b.mn+=a.ed; k.mn=min(a.mn,b.mn); k.mx=max(a.mx,b.mx); k.ed=a.ed+b.ed; k.v=(a.mn<b.mn?a.v:b.v); k.l=a.l,k.r=b.r; return k; } void up(int now){ tr[now]=bin(tr[ls],tr[rs]); } void build(int now,int l,int r){ if(l==r){ tr[now].ed=1-d[l]; tr[now].v=a[l]; tr[now].l=tr[now].r=r; tr[now].mx=d[l]; return ; } int mid=(l+r)/2; build(ls,l,mid); build(rs,mid+1,r); up(now); } void add(int now,int l,int r,int x,int v){ if(l==r){ tr[now].ed-=v; tr[now].mx+=v; return ; } int mid=(l+r)/2; if(x<=mid) add(ls,l,mid,x,v); else add(rs,mid+1,r,x,v); up(now); } void chgv(int now,int l,int r,int x,int v){ if(l==r){ tr[now].v=v; return ; } int mid=(l+r)/2; if(x<=mid) chgv(ls,l,mid,x,v); else chgv(rs,mid+1,r,x,v); up(now); } node ask(int now,int l,int r,int x,int y){ if(x<=l&&r<=y) return tr[now]; int mid=(l+r)/2; if(y<=mid) return ask(ls,l,mid,x,y); if(x>mid) return ask(rs,mid+1,r,x,y); return bin(ask(ls,l,mid,x,y),ask(rs,mid+1,r,x,y)); } node fidl(int now,int l,int r,int x,int v,node rt){ if(l==r) return tr[now]; int mid=(l+r)/2; if(x<=mid) return fidl(ls,l,mid,x,v,rt); if(r<=x) { if(bin(tr[now],rt).v<v) return tr[now]; node k=bin(tr[rs],rt); if(k.v>=v) return fidl(rs,mid+1,r,x,v,rt); else return bin(fidl(ls,l,mid,x,v,k),tr[rs]); } node k=fidl(rs,mid+1,r,x,v,rt); if(k.v>=v) return k; return bin(fidl(ls,l,mid,x,v,k),k); } node fidr(int now,int l,int r,int x,int v,node lf){ if(l==r) return tr[now]; int mid=(l+r)/2; if(x>mid) return fidr(rs,mid+1,r,x,v,lf); if(x<=l){ if(bin(lf,tr[now]).v<=v) return tr[now]; node k=bin(lf,tr[ls]); if(k.v>v) return fidr(ls,l,mid,x,v,lf); else return bin(tr[ls],fidr(rs,mid+1,r,x,v,k)); } node k=fidr(ls,l,mid,x,v,lf); if(k.v>v) return k; return bin(k,fidr(rs,mid+1,r,x,v,k)); } node nxt(int now,int l,int r,int x,node lf){ if(l==r) return tr[now]; int mid=(l+r)/2; if(x>mid) return nxt(rs,mid+1,r,x,lf); if(x<=l){ if(bin(lf,tr[now]).mn>=0) return tr[now]; node k=bin(lf,tr[ls]); if(k.mn<0) return nxt(ls,l,mid,x,lf); else return bin(tr[ls],nxt(rs,mid+1,r,x,k)); } node k=nxt(ls,l,mid,x,lf); if(k.mn<0) return k; return bin(k,nxt(rs,mid+1,r,x,k)); } }t; int dfn; void dfs(int now){ dfn++; for(int t:v[now]) dfs(t); d[dfn]++; } signed main(){ n=read()+1,q=read(); a[1]=inf,st[tp=1]=1; for(int i=2;i<=n;i++){ a[i]=read(); while(tp>1&&a[st[tp]]<a[i]) tp--; v[st[tp]].pb(i); st[++tp]=i; } a[n+1]=inf; dfs(1); t.build(1,1,n+1); for(int i=1;i<=q;i++){ int op=read(); if(op==1){//修改 int x=read()+1,v=read(); int s=t.fidl(1,1,n+1,x-1,a[x]+v,{inf,0,0}).ed-1; t.add(1,1,n+1,x-1,s); while(s>0){ node k=t.nxt(1,1,n+1,x,{inf,0,0}); k.r--; if(-k.mn<=s){ t.add(1,1,n+1,k.r,k.mn); s+=k.mn; }else{ t.add(1,1,n+1,k.r,-s); s=0; } } int r=t.fidr(1,1,n+1,x,a[x],{inf,0,0}).r-1; int nwr=t.fidr(1,1,n+1,x,a[x]+v,{inf,0,0}).r-1; t.add(1,1,n+1,r,-1); t.add(1,1,n+1,nwr,1); a[x]+=v; t.chgv(1,1,n+1,x,a[x]); }else{//查询 int x=read()+1; int l=t.fidl(1,1,n+1,x-1,a[x],{inf,0,0}).l+1; int r=t.fidr(1,1,n+1,x,a[x],{inf,0,0}).r-1; if(l==r) pc('0'); else{ node p=t.ask(1,1,n+1,l,r-1); write(max(p.mx-1,p.ed)); } pc('\n'); } } l_write(); return 0; } ```