差分数组与树上差分

假设我们现在需要维护一段区间,实现对一段区间[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即可

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值