假设我们现在需要维护一段区间,实现对一段区间[L,R]的数的加/减,以及对区间中某个数的查询
最简单的方法把这段区间的数都记录到数组a[N]中,每次修改都对区间[L,R]的每一个数进行加减操作,然后在查询第k个数的时候返回a[k]
在上述算法中,修改操作的复杂度是o(n),而查询操作的复杂度是o(1)
这种操作在查询次数大,修改次数小的时候很高效
但在修改次数大,查询次数少的时候,就显得很慢了
那么在这个时候,我们可以使用差分数组来维护这段区间
差分数组:
我们定义一个新的数组d[N]
且d[i]满足转移式:
d[i]=a[i]-a[i-1],且d[0]等于0
也就是说,d[i]表示的是a[i]比其前面一个数大d[i]
那么在这个时候
假设我们需要对区间[l,r]的数都进行加k的操作,显然,区间[l+1,r]的d[i]的值都不变,因为在这个区间内的a[i]和a[i-1]都同时加上了k,所以他们的差值d[i]保持不变
而a[l]应该会比a[l-1]大k,a[r+1]应该会比a[r]小k
所以d[l]+=k;而d[r+1]-=k;
这样,就可以把对区间修改的时间复杂度降低到o(1)了
那么怎么进行查询呢?
观察d[i]的定义式,我们可以知道,a[x]=d[0]+d[1]+......d[x-1]+d[x]
所以,我们只需要o(n)的时间复杂度,把d数组的前x项求和,就可以得到a[x]的值了
树上差分:
上面提到的差分数组可以用来维护一维的区间
那么用同样的思路,我们也可以用差分的方法来维护树上的一段路径
我们现在定义树上的差分数组d[N]
假设现在有节点u和ls,rs,ls,rs是u的左右儿子
其中d[u]表示结点u的权值,那么d[u]=d[ls]+d[rs]
而d[ls]又可以分为d[ls的左儿子]+d[ls的右儿子]
这样无限递归直到叶子结点
我们类比一下上面的差分数组的单点查询,可以发现这非常的相似
我们对于树上一段路径的修改,是否也可以通过单点修改而降低时间复杂度呢?
答案是肯定的
我们这里将修改分为两种:
1.点修改
假定我们现在需要把路径(x,y)上的点的权值全部加k
对于x的父亲而言,只要x加了k,那么他自己自然也就加了k,这对x的父亲的父亲一样适用
对于y的父亲也是同理
那么x到y的路径中,一定包含了x的若干个祖先,y的若干个祖先,以及x和y的lca(最近公共祖先)
那么对于lca而言,他的值等于x的某个祖先的权值+y的某个祖先的权值,也就是说加了两次k
所以lca的sum应该减去k来抵消多加的一个k
而对于lca的父亲fa而言,他并没有加多一个k,但由于lca加多了一个k,所以fa也应该减去一个k
由此可以得到,对于修改路径(x,y)上的点权,假定为加k,那么就是:
d[x]+=k;d[y]+=k;d[lca(x,y)]-=k;d[fa(lca(x,y))]-=k;
2.边修改:
假定我们现在需要把路径(x,y)上的边权全部加k
由于对边的权值操作比较困难,我们将其下方到点权进行
对于这个下方操作,我们定义:
假定a是儿子,b是父亲,w是连接a与b的一条边,那么w的边权就等于d[b],同时,d数组的定义与上面的点修改中提到的一致
那么我们只需要让d[x]+=k;d[y]+=k;
这样子的话,对于路径(x,y)上的连接x的祖先们的边,连接y的祖先们的边的权值自然也就加上了k
但是对于lca(x,y)而言,由于他一条连接着x的祖先的边权加了k,另一条连着y的祖先的边权也加了k,为了不影响连接lca与其父亲的边权,所以d[lca(x,y)]需要减去2个k
总结就是:
d[x]+=k;d[y]+=k;d[lca(x,y)]-=2*k;
对于树上的单点修改,我们使用dfs即可