树上差分


树上差分

在讲树上差分之前,首先需要知道树的以下两个性质:

  (1)任意两个节点之间有且只有一条路径。

  (2)根节点确定时,一个节点只有一个父亲节点

  这两个性质都很容易证明。那么我们知道,如果假设我们要考虑的是从u">uv">v的路径,u">uv">v的lca">lca是a">a,那么很明显,如果路径中有一点u">u′已经被访问了,且u">ua">a,那么uu">'的父亲也一定会被访问,这是根据以上性质可以推出的。

u">v">u">v">lca">a">u">u">a">u">所以,我们可以将路径拆分成两条链,u">u->a">a和a">a->v">v。那么树上差分有两种常见形式:(1)关于边的差分;(2)关于节点的差分。

  ①关于边的差分:

  将边拆成两条链之后,我们便可以像差分一样来找到路径了。用cf[i]">cf[i]代表从i">i到i">i的父亲这一条路径经过的次数。因为关于边的差分,a">a是不在其中的,所以考虑链u">u->a">a

cf[i]">i">i">a">u">a">则就要使cf[u]++">cf[u]++cf[a]">cf[a]??。然后链a">a->v">v,也是cf[v]++">cf[v]++cf[a]">cf[a]??。所以合起来便是cf[u]++">cf[u]++cf[v]++">cf[v]++cf[a]=2">cf[a]?=2。然后,从根节点,对于每一个节点x">x,都有如下的步骤:

  (1)枚举x">x的所有子节点u">u

  (2)dfs">dfs所有子节点u">u

  (3)cf[x]+=cf[u]">cf[x]+=cf[u]

  那么,为什么能够保证这样所有的边都能够遍历到呢?因为我们刚刚已经说了,如果路径中有一点u">uu′已经被访问了,且u">ua">a,那么u">u的父亲也一定会被访问。所以u">u被访问几次,它的父亲也就因为u">u被访问了几次。

u">u">a">u">u">u">所以就能够找出所有被访问的边与访问的次数了。路径求交等一系列问题就是通过这个来解决的。因为每个点都只会遍历一次,所以其时间复杂度为Θ(n)">Θ(n).

  ②关于点的差分:

  还是与和边的差分一样,对于所要求的路径,拆分成两条链。步骤也和上面一样,但是也有一些不同,因为关于点,u">uv">v的lca">lca是需要包括进去的,所以要把lca">lca包括在某一条链中,用cf[i]">cf[i]表示i">i被访问的次数。

u">v">lca">lca">cf[i]">i">最后对cf">cfcf数组的操作便是cf[u]++">cf[u]++cf[v]++">cf[v]++cf[a]">cf[a]??,cf[father[a]]">cf[fa[a]]??。其时间复杂度也是一样的Θ(n)">Θ(n).

我们把路径拆成u->a,a->u
每一部分从下向上走
对于u->a部分,只需cf[u]++,cf[a]--

对于a->v部分,只需cf[v]++,cf[a]--

然后发现a被修改了两次,所以我们要对a取消一次重复的差分标记

cf[fa]++,cf[fa[a]]??