线段树维护单调栈
前言
一天改了三道线段树。真tm刺激。
数据结构就像女朋友,几天不见就不理我了。
没事干来写个总结。
正经
线段树啥都能干系列。
比方说有个毒瘤DP JZOJGod Knows
要求你在线性以下的复杂度内查一个区间内维护成单调栈的值的最大值。
或者单调序列的最长长度。然后就比较狗屎了。
这时候你就需要一些神奇的操作来维护了。
考虑什么样的东西可以维护。
发现这时候线段树上左右儿子是互相影响的。
就以查询单调上升序列长度为例。
一般线段树的区间是不保证单调的。但是有比较就有伤害。
右儿子:你看你一个左儿子比我还高合适么。
左儿子:.......
于是左儿子就被右儿子砍了。
具体来说就是实现一个cal(尻)函数。
首先这个东西可以在父亲节点让自己的右儿子砍左儿子(和谐)
考虑这个cal函数cal(k,w)
k是线段树节点,w是一个值代表我这个区间大于w的数被弹掉后的上升序列长度。
然后接下来一定要注意复杂度,线段树的log查询都是靠链状延伸保证的。
因此只能递归下去一边,这样cal一次才是log的。
那么这个时候就可以考虑右儿子的最小值和这个w之间的关系了,如果右儿子最小
值都比w大,那么直接GG,右儿子肯定全弹没了, 递归左边。
如果右儿子的最小值比w小,那么就可以递归右边,但是还要左边的怎么算?
一定不能递归,需要在节点上维护这么一个信息,
dw表示左儿子被右儿子砍完后的长度。
那么这个cal函数就完美了。
当右儿子最小值比w小,那么递归右边并加上左儿子的dw
int calfr(int k,int l,int r,int w) { if(l==r) return tr[k].w>w; int mid=l+r>>1; if(tr[k<<1|1].w<w) return calfr(k<<1,l,mid,w); else return tr[k<<1].dw+calfr(k<<1|1,mid+1,r,w); }
Q:那么这个cal函数干啥用。
A:维护dw。
注意不要打脸了这个。
听我解释。这个cal函数用的时候是回溯的时候用,此时下边的dw应该都更新完了,
然后用来更新当前节点的dw。
那么怎么查询呢?
给定一个区间[x,y],询问这个区间从做到右递增序列的长度。
还是那个思路先把区间打到log个线段树区间上,然后就要把区间接起来。。。
不是还有个尻函数呢吗
想一下的话应该是从右往左接,
具体就是查到右边的答案和最小值,把右边区间的最小值作为参数传入,
然后查询左边区间的俩值,在查左边的左边。
第一次递归调用的时候传一个极大值就行了。
所以这么写线段树就应该先递归右边了。
推荐一种线段树写法。
然后就没了。
一次尻函数是$log$的,总复杂度$O(nlog^2n)$
st asknw(int k,int l,int r,int x,int y,int w) { if(x>y) return (st){0,0}; if(l==x&&r==y) { int sw=calfr(k,l,r,w); return (st){sw,tr[k].w}; } int mid=l+r>>1; if(x>mid) return asknw(k<<1|1,mid+1,r,x,y,w); else if(y<=mid) return asknw(k<<1,l,mid,x,y,w); else { st tmp=asknw(k<<1|1,mid+1,r,mid+1,y,w); st now=asknw(k<<1,l,mid,x,mid,max(w,tmp.id)); return (st){tmp.w+now.w,max(tmp.id,now.id)}; } }