树上差分
近年的NOIP,对于树上差分的题目时有出现:(NOIP 2015《运输计划》,NOIP2016《天天爱跑步》)。
这些题目都要知道在树上从某个点到另一个点的所有路径。
但是,暴力求解这种题目经常会TLE。
这种题目需要使用树上差分。
在讲树上差分之前,首先需要知道树的以下两个性质:
- 任意两个节点之间有且只有一条路径。
- 一个节点只有一个父亲节点
所以:如果假设我们要考虑的是从u到v的路径,u与v的lca是a,那么很明显,我们可以将路径拆分成两条链,u→a和a→v。那么树上差分有两种常见形式:
- 关于边的差分;
- 关于节点的差分。
树上差分
树上差分有两种实现方法,第一种是转换为线性差分,第二种是利用最近公共祖先(lca)进行差分。第一种方法用于修改子树,第二种方法用于修改路径
- 第一种差分
求出dfs序,以dfs序为基础进行差分
修改以i为根的子树的权值,在dfs序中相当于修改连续区间,可以O(1)实现
统计每个节点的权值,即求前缀和
- 第二种差分
直接进行差分,但差分维护的是每个点到根节点的路径
修改i到根路径上的权值,只要修改d[i]即可,复杂度O(1)
修改从x到y路径上的权值,可以先求它们的lca点z,将d[x]和d[y]分别相加,再减去2*d[z]即可,复杂度O(1)
统计每个节点的权值,即dfs求子树的差分和,时间复杂度O(n)
- 关于边(边(u,fa(u))信息记在节点x上)的差分:
将边拆成两条链之后,我们便可以像差分一样来找到路径了。
因为关于边的差分,a是不在其中的,所以考虑链u→a,则就要使d[u]++,d[a]--。
然后链v→a,也是d[v]++,d[a]--。
所以合起来便是d[u]++,d[v]++,d[a]-=2。
关于边(边(u,fa(u))信息记在节点u上)的差分:
从根节点,对于每一个节点x,都有如下的步骤:
- 枚举x的所有子节点u
- dfs所有子节点u
- d[x]+=d[u]
因为每个点都只会遍历一次,所以其时间复杂度为O(n).
- 关于点的差分:
还是与和边的差分一样,对于所要求的路径,拆分成两条链。
步骤也和上面一样,但是也有一些不同,因为关于点,u与v的lca是需要包括进去的,所以要把lca包括在某一条链中,最后对d数组的操作便是d[u]++,d[v]++,d[a]--,d[father[a]]--。
其时间复杂度也是一样的O(n)。