2018年3月7日训练日记
RMQ问题中的ST算法:(摘自某位大佬)
ST(Sparse Table)算法
定义:f[i][j]表示i到i+2^j-1这段区间的最大值。
预处理:f[i][0]=a[i]。即i到i区间的最大值就是a[i]。
状态转移:将f[i][j]平均分成两段,一段为f[i][j-1],另一段为f[i+2^(j-1)][j-1]。
两段的长度均为2^j-1。f[i][j]的最大值即这两段的最大值中的最大值。
得到f[i][j]=max(f[i][j-1],f[i+2^(j-1)][j-1])。
void RMQ(int N){
/*注意外部循环从j开始,
因为初始状态为f[i][0],
以i为外层会有一些状态遍历不到。*/
for(int j=1;j<=20;j++)
for(int i=1;i<=N;i++)
if(i+(1<<j)-1<=N)
f[i][j]=max(f[i][j-1],f[i+(1<<(j-1))][j-1]);
}
当然求区间和就肯定不行了。这也就是RMQ的限制性。
因为区间的长度为j-i+1,所以可以取k=log2(j-i+1)。
则RMQ(A,i,j)=max(f[i][k],f[j-2^k+1][k])。
若在一棵树上进行路径的权值修改,询问路径权值和、路径权值极值,树上倍增和树链剖分都能解决后两种问题,而且树上倍增要比树链剖分代码好打,但是树链剖分能处理权值修改问题(第一种问题),而树上倍增不能。树剖的用处比树上倍增多得多,树上倍增能做的题树剖一定能做,反过来就不行了。
树剖我记得之前看过一点,但是去翻了自己的博客居然没找到,只有自己当时打的模板。。。以下树剖内容来自(路人黑的纸巾)
轻儿子和重儿子
设xsize表示以x为根的子树的节点个数
对于每个非叶子节点y,y的儿子所有中size最大的儿子就是重儿子(两个或以上都最大的话,任意选一个)
而y其它的儿子,都是轻儿子 (轻儿子通常没有用,我们可以不去考虑它)
重边、轻边和重链
重儿子与其父亲之间的路径,就是重边
不是重边的边均为轻边
多条重边相连为一条重链
相关性质:
若x是轻儿子,则有xsize<=father(x)size2
从根到某一点的路径上,不超过log2n条重链,不超过log2n条轻链
预处理
其次再用一个dfs剖分,第二个dfs维护什么呢?
- 以优先走重边的原则,遍历出来一个dfs序
- xtop表示x位于的重链上的顶端节点编号
- to_tree[x]表示x在线段树中的位置
- 对于线段树中的第y个位置,to_num[y]为它对应的dfs序中的位置
剖分完以后,每一条重链相当于一个区间,我们维护处理完的数据结构即可
我们把上面那棵树剖分完以后线段树中的点顺序是这样的:
1,3,6,10,8,9,7,2,5,4 (加粗的点是在重链上的点)
在同一条重链上的点,在线段树的顺序中要连在一起(否则怎么维护呢)
剖树的时间复杂度 O(n)O(n)
维护以及在线操作
因为已经搞过to_tree了,通过to_tree的标号在线段树中用单点修改即可
时间复杂度是线段树修改的O(log2n)O(log2n)
询问xx到yy路径的极值(或者路径和等等),和求LCALCA一样边向上跳边记录答案
这里记录的答案通过查询数据结构得到
用最普通的数据结构线段树来维护树链剖分的时间复杂度是O(n+qlog2n)
hiho600题 go!