由于网上的关于差分的资料比较少,因此我根据我本身的理解编写这篇博文。若是你有什么问题,能够联系我。算法
--------------------------------------------------------------------------------------------------------编程
有这样一道题目:给你一个$m×n$的矩阵,而后使用$k$块地毯铺地。每片地毯都给出左下角和右上角坐标。问全部地毯铺完以后,还有多少个整点(所谓整点,即横、纵坐标均为整数的点)没有被地毯覆盖。数组
固然,咱们很容易写出以下的暴力程序(伪代码):优化
solve(){ 暴力枚举每张地毯 将全部被覆盖的点均作上标记 最后再枚举全部整点,若未被标记则ans+1 }
可是,很明显,这个算法并不能拿到满分,由于它的空间复杂度为$\Theta(nm)$,可是时间复杂度却可能达到$\Theta(mnk)$,对通常的比赛来讲确定会没法经过。调试
固然,有些大佬可能会说:开$m$棵线段树能够解决此问题。确定是能够的,可是对于$NOIp$这种比赛来讲,考试时间比较紧促,我是不太赞同这种算法的,由于这样子编程复杂度过高,甚至可能出现没法调试成功而影响了其它的题目或是影响本身的心情。blog
那么,咱们应该如何优化这个算法呢?咱们考虑一下,主要的时间就是用在枚举地毯和枚举被地毯覆盖的整点上,咱们能够对这里进行优化。由于对于每块地毯,每一行,覆盖的确定是一个连续区间。因此咱们能够考虑一下前缀和。经过前缀和的方式考虑每一个点被地毯覆盖的次数。以下面表格所示,假如地毯覆盖了$[2,6]$一段($1$表示地毯在该行的左端点,其中表格第一行为数组下标,第二行为数组值):table
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 |
经过对数组求前缀和,咱们便能获得如下的表格:class
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
咱们便会发现,经过这种前缀和的形式,可以在$\Theta(1)$的时间里,实行对一行从某个左端点开始一段区间的修改。可是,咱们这个题目中地毯除了有左边界,还有右边界啊?没关系,咱们在右边界后面再减去$1$,就能够保证没有被覆盖到的地方不会受到影响。而因为右端点也包括在被覆盖的范围内,因此咱们要让$r+1$减去$1$.用上面的表格,若是地毯在该行覆盖了$[2,6]$一段,咱们就将原数组修改为如下所示:循环
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
0 | 1 | 0 | 0 | 0 | 0 | -1 | 0 |
求前缀和以后,数组就变成以下:遍历
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
0 | 1 | 1 | 1 | 1 | 1 | 0 | 0 |
这样子咱们就会发现,全部被地毯覆盖的点都会变成$1$,而对于每一行,这种操做都是$\Theta(1)$的,因此k块地毯所有考虑完毕的时间复杂度为$\Theta(kn)$,最后每行作前缀和的时间复杂度为$\Theta(nm)$,这样子便对以上暴力算法进行了有效的优化。因此咱们能够写出如下的代码(伪代码):
solve(){ 从1号地毯考虑到第k块地毯 对于每一块地毯,从右上角坐标行数循环到左下角行数 将每一行进行修改 对差分数组求前缀和并累计未被覆盖地毯的点 }
近年的$NOIp$,彷佛对于树上差分的题目考察愈来愈热(参见$2015$年提升组 运输计划,$2016$年提升组 每天爱跑步)。这些题目都要知道在树上从某个点到另外一个点的全部路径。可是,暴力求解这种题目常常会$TLE$。这种题目须要使用树上差分。在讲树上差分以前,首先须要知道树的如下两个性质:
(1)任意两个节点之间有且只有一条路径。
(2)根节点肯定时,一个节点只有一个父亲节点
这两个性质都很容易证实。那么咱们知道,若是假设咱们要考虑的是从$u$到$v$的路径,$u$与$v$的$lca$是$a$,那么很明显,若是路径中有一点$u'$已经被访问了,且$u'$≠$a$,那么$u$'的父亲也必定会被访问,这是根据以上性质能够推出的。因此,咱们能够将路径拆分红两条链,$u$->$a$和$a$->$v$。那么树上差分有两种常见形式:(1)关于边的差分;(2)关于节点的差分。
将边拆成两条链以后,咱们即可以像差分同样来找到路径了。用$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'$被访问了几回。因此就可以找出全部被访问的边与访问的次数了。路径求交等一系列问题就是经过这个来解决的。由于每一个点都只会遍历一次,因此其时间复杂度为$\Theta(n)$.
仍是与和边的差分同样,对于所要求的路径,拆分红两条链。步骤也和上面同样,可是也有一些不一样,由于关于点,$u$与$v$的$lca$是须要包括进去的,因此要把$lca$包括在某一条链中,用$cf[i]$表示$i$被访问的次数。最后对$cf$数组的操做即是$cf[u]++$,$cf[v]++$,$cf[a]--$,$cf[father[a]]--$。其时间复杂度也是同样的$\Theta(n)$.
--------------------------------------------------------------------------------------------------------
经过以上的描述,若是你仍是不太能理解,那么如下两个题目可能能够帮助你理解:
USACO 最大流(树上差分)https://www.luogu.org/problem/show?pid=3128
NOIp2015 运输计划(树上差分+二分)https://www.luogu.org/problem/show?pid=2680