前缀和、差分与树上差分

1.结论

1.1 前缀和:前缀和可以通过对一个序列进行O(n)的预处理后,在O(1)时间内求出任意一个子序列的和。

1.2 差分:可以用于求解多次区间修改与区间询问的题型,例如多次次给[ l , r ] 内所有数 + val,就可以用差分以及前缀和来优化。区间操作O(1),区间询问O(n)处理,O(1)查询。

1.3 树上差分:同样的,如果我们有若干次操作与若干次询问,每次操作对从u 到 v 路径上所有节点加一个值,那么我们用树上差分可以将时间复杂度控制在O(1)上,询问同样是O(n)处理,O(1)查询。

2.前缀和

2.1 问题描述:给出一串长度为 n 的数列a1,a2,a3......an,再给出m个询问,每次询问给出L,R两个数,要求给出区间[L,R]里的数的和。

2.2 朴素思路:最简单的做法就是设置一个变量sum,将所有处在区间内的数累加到 sum 上,最后输出sum的值即为答案。但是这样的时间复杂度为O(nm),难以接受。

2.3 前缀和:假设我们开一个数组sum,sum[ i ]存放数列中前 i 个元素的和,即sum[i] = \sum_{j = 1}^{i}num[i],那么每次询问,我们可以直接给出答案ans = sum[ R ] - sum[ L - 1 ]。这样写预处理的时间复杂度为O(n),每次询问O(1)。

3.差分

3.1 问题描述:给出一串长度为 n 的数列a1,a2,a3......an , 给出k次操作,每次操作给出L、R、val,要求对[L , R]内所有元素加上val,再给出m个询问,每次询问给出L,R两个数,要求给出区间[L,R]里的数的和。

3.2 朴素思路:很容易想到的做法就是每次给出L R 和 val后,我们就挨个对[ L, R ] 内所有元素+val,再在询问时将答案挨个加起来。这样时间复杂度为O(kmn),不能接受。

3.3 差分:

既然之前我们可以用前缀和来进行优化,这里我们是否也可以用前缀和试着优化呢?

假设 sum 数组初始值全是为 0 ,如果我们要将 [ L , R ] 上所有元素 + val,那么我们设sum[ L ] = val,sum[ R ] = -val。这样一来当我们询问时,我们对sum数组进行一次更新,即sum[ i ] = sum[ i - 1 ] + ai;那么此时的sum数组就是在进行 k 次操作后的数组的前缀和数组。

根据2.3我们可知,这样查询的效率为O(1),于是时间复杂度就成了O(k+n)。

4.树上差分

4.1 问题描述:

  • 给定一棵有N个点的树,所有节点的权值初始时都为0。 
  • 有K次操作,每次指定两个点u , v,将 u 到 v 路径上所有点的权值都+1。 
  • 请输出K次操作完毕后权值最大的那个点的权值。

4.2 朴素思路:不用多想,最暴力的做法就是我们找到 u 到 v 路径上的所有点并+1(可以利用lca)。最后再遍历所有点查找权值最大的点并输出。这样时间复杂度为O(KN),这还不包括 lca 查找路径的时间。

4.3 求u到v的路径:求那么我们知道,如果假设我们要考虑的是从u到v的路径,u 与 v 的 lca 是 a ,那么很明显,如果路径中有一点 u′ 已经被访问了,且 u′ ≠ a ,那么 u' 的父亲也一定会被访问。所以,我们可以将路径拆分成两条链,u -> a 和 a -> v。

4.4 树上差分:常见的树上差分有两种形式,即

  • 关于边的差分
  • 关于点的差分。

4.4.1 关于边的差分:

将边拆成两条链之后,我们便可以像差分一样来找到路径了。用 cf[ i ] 代表从i到i的父亲这一条路径经过的次数。因为关于边的差分,a 是不在其中的,所以考虑链 u -> a,则就要使cf[ u ]++,cf[ a ]−−。然后链a -> v,也是cf[ v ]++,cf[ a ]−−。所以合起来便是cf[ u ]++,cf[ v ]++,cf[ a ]−=2。然后,从根节点,对于每一个节点x,都有如下的步骤:

  (1)枚举x的所有子节点u
  (2)dfs所有子节点u
  (3)cf[ x ] + = cf[ u ]
  那么,为什么能够保证这样所有的边都能够遍历到呢?因为我们刚刚已经说了,如果路径中有一点u′已经被访问了,且u′≠a,那么u′的父亲也一定会被访问。所以u′被访问几次,它的父亲也就因为u′被访问了几次。所以就能够找出所有被访问的边与访问的次数了。路径求交等一系列问题就是通过这个来解决的。因为每个点都只会遍历一次,所以其时间复杂度为Θ(n).

4.4.2 关于点的差分:

还是与和边的差分一样,对于所要求的路径,拆分成两条链。步骤也和上面一样,但是也有一些不同,因为关于点,u与v的lca是需要包括进去的,所以要把lca包括在某一条链中,用cf[ i ] 表示 i 被访问的次数。最后对 cf 数组的操作便是cf[ u ]++,cf[v]++,cf[ a ]−−,cf[ father[a] ]−−。其时间复杂度也是一样的Θ(n).

 

你可能感兴趣的:(Algorithm,数据结构)