这两周在学树剖。
先扔个模板
有一类题目,要求实现一类在树上的操作,比如:
修改/求 树上某 节点/边权 的(最)值;
修改/求 树上某 节点/边权 及其子树上所有节点的(最)值;
修改/求 树上某两点路径间的 节点/边权 的(最)值;
乍一看似乎用线段树就可以实现,但是如果仔细想想,可以发现单凭线段树是无法解决的。
对于这类题目,常用的解决方法是树链剖分。
树链剖分(节选自starszys博客):
相关定义:
重儿子:siz[u]为v的子节点中siz值最大的,那么u就是v的重儿子。
轻儿子:v的其它子节点。
重边:点v与其重儿子的连边。
轻边:点v与其轻儿子的连边。
重链:由重边连成的路径。
轻链:轻边。
树链,就是树上的路径。剖分,就是把路径分类为重链和轻链。
记siz[v]表示以v为根的子树的节点数
d[v]表示v的深度(根深度为1)
top[v]表示v所在的重链的顶端节点
f[v]表示v的父亲
son[v]表示与v在同一重链上的v的儿子节点(重儿子)
id[v]表示:
1.v与其父亲节点的连边(v的父边)在线段树中的位置;
2.v在线段树中的位置;
只要把这些东西求出来,就能用logn的时间完成上述问题中的操作。
实际上,只需要两次dfs就可以求出上述变量。
dfs1(dfs):求出f、d、siz、son
dfs2(build):求出id与top(详见模板)
然后将原树中各点的值update到线段树中,进行各种操作。
基本操作:
1.修改/求 树上某 节点/边权 的值:根据id简单query()即可;
2.修改/求 树上某两点路径间的 节点/边权 的(最)值:顺着top[],将top[]深度较大的节点向上调整,调整过程中进行区间查询即可;
3.修改/求 树上某 节点/边权 及其子树上所有节点的(最)值:一般来说是用DFS序+线段树,但是经过树链剖分,任意一棵子树内的节点的id必然是连续的,区间修改[id[v],id[v]+siz[v]-1]即可;
时间复杂度:
既然树剖这么好用,那它的时间复杂度如何呢?
可以证明,树链剖分执行一次修改/查询的时间复杂度是O(logn·logn)。
经过上面的学习,可以发现树链剖分的结构是沿着链向上跳+线段树修改/查询。
线段树修改查询操作的时间复杂度是O(logn),而在树链上每走一条轻边,子树大小就/=2,所以最多走log n条轻边。
推荐题目:
模板题:BZOJ4034、BZOJ1036(LZOJ21004)、BZOJ2243、POJ3237
稍难:BZOJ3531、BZOJ3626、NOIp2015day1T3、NOIp2016天天爱跑步