【算法学习】算法技巧之差分 一维、二维差分的实现与应用


一、差分介绍与代码模板

如果我们需要维护的数据是“两个相邻数据之差”,这就是差分。令 d i = a i − a i − 1 d_{i}=a_{i}-a_{i-1} di=aiai1,则称 d d d a a a的差分数组。也可以写成 Δ a i = a i − a i − 1 \Delta a_{i} = a_{i} - a_{i-1} Δai=aiai1

差分的写法与前缀和专栏的那篇介绍文章差不多,我们使用的数据数组 a a a 和差分数组 d d d 都是从1开始:

【差分模板】:

void getDiff(int a[], int n) { //a[0]=0, 使用[1,n]
	for (int i = 1; i <= n; ++i) 
		d[i] = a[i] - a[i - 1]; //d[0]=0 
}

二、从差分到原数组

其实从前缀和也可以很轻易的求出原数组,不过是求区间和的区间的左右两端点相等罢了(即 a [ i ] = s u m [ i ] − s u m [ i − 1 ] a[i] = sum[i] - sum[i - 1] a[i]=sum[i]sum[i1])。

同样的思想,我们也可以拿着差分数组 d d d推出 a a a数组。而且 a [ i ] = a [ i − 1 ] + d [ i ] a[i] = a[i - 1] + d[i] a[i]=a[i1]+d[i] a i a_{i} ai相当于是差分数组 d d d的前缀和 s u m [ i ] sum[i] sum[i] … 不说相当于,其实, a a a数组就是 d d d数组的前缀和数组

如果要求 a a a数组的区间和,怎么做呢?(^_^)

所以,从我们约定的前缀和的写法(方便递推求前缀和数组)和求差分这两种角度来看,就写 a [ 0 ] = 0 a[0]=0 a[0]=0

简单的举例:

a   : 0 | 3 7  5 8 (a[0]默认为0)
d   : 0 | 3 4 -2 3
sum : 0 | 3 7  5 8

很容易想明白,现在我在0楼,上了3楼,又上了4层楼,再往下走了两层楼,然后又上了三层楼,那么我现在位于0+3+4-2+3=8楼。

代码如下:

void getA(int p[], int n) { //d[0]=0, 使用[1,n]
	for (int i = 1; i <= n; ++i) 
		a[i] = a[i - 1] + d[i];
}

前缀和可以让人以O(1)的时间求出一个区间的和。差分的用途也差不多,用来优化算法,甚至可以优化掉一个次方的复杂度。

下面具体介绍其应用。

三、一维差分的应用【区间加】

给定一个序列 a a a,其初值全为0,有很多(m)次操作,允许离线,形如: A l r k A \quad l \quad r \quad k Alrk,将 a [ l , r ] a_{[l,r]} a[l,r]中的每个值加上 k k k。为方便,这里的 l ≥ 1 , r ≥ 1 l \geq 1, r \geq 1 l1,r1。最后输出整个数组,复杂度要求 O ( n ) O(n) O(n)

举例如下:

a : 0 | 0 0 0 0 0 0

A 1 2 3
-> a : 0 | 3 3 0 0 0 0

A 2 5 -2
-> a : 0 | 3 1 -2 -2 -2 0

A 5 6 3
-> a : 0 | 3 1 -2 -2 1 3

最简单的想法,就是对原数组进行操作,每次对区间 [ l , r ] [l,r] [l,r]进行相应的操作,时间为 O ( m n ) O(mn) O(mn),m很大时甚至会达到平方的复杂度。下面的做法,可以真正做到 O ( n ) O(n) O(n)

不难发现,区间 [ l , r ] [l,r] [l,r]加k,实际上发生了如下的两件事:

  • a [ l ] a[l] a[l]比前一个元素多了 k k k
  • a [ r + 1 ] a[r + 1] a[r+1]比前一个元素少了 k k k;(不用大了小了的说法,会引起误解)
    在这里插入图片描述

在差分数组 d d d的基础上面,可以对应转换为两个操作:

  • d [ l ] + = k d[l] += k d[l]+=k
  • d [ r + 1 ] − = k d[r + 1] -= k d[r+1]=k

即,一次区间加,只修改这两个元素,区间内部的数据之间的差值没有变化,对应于差分数组相应区别不变。

最后利用上面的(求前缀和)方法,从 d d d数组求出 a a a数组,即为答案。

例子:

a : 0 | 0 0 0 0 0 0
d : 0 | 0 0 0 0 0 0

A 1 2 3
-> a : 0 | 3 3  0 0 0 0
-> d : 0 | 3 0 -3 0 0 0

A 2 5 -2
-> a : 0 | 3  1 -2 -2 -2 0
-> d : 0 | 3 -2 -3  0  0 2

A 5 6 3
-> a : 0 | 3  1 -2 -2  1 3
-> d : 0 | 3 -2 -3  0  3 2  (r + 1 > n, 计不计算都行, p数组开大一些)

区间加

void add(int l, int r, int k) {
	d[l] += k;
	d[r + 1] -= k;
}

void getA() {
	for (int i = 1; i <= n; ++i) //预先指定a[0]=0
		a[i] = a[i - 1] + d[i];

如果是单点加,用上面的add完全没问题:
d x + = k , d x + 1 − = k . d_{x} += k, d_{x+1} -= k. dx+=k,dx+1=k.

本质和区间加一模一样。


四、一维差分应用拓展【区间加等差+单点查询】

给定一个序列 a a a,其初值全为0,有很多(m)次操作,操作有两种,强制在线,形如:

  • 区间加等差数列: A l r f w A \quad l \quad r \quad f \quad w Alrfw,将 a [ l , r ] a_{[l,r]} a[l,r]中的每个元素,加上首项为 f f f、公差为 w w w的等差数列对应的元素,即 a [ l ] + = f 1 a_{[l]} += f_{1} a[l]+=f1 a [ l + 1 ] + = f 2 a_{[l + 1]} += f_{2} a[l+1]+=f2,… 。
  • 单点查询: x x x ,求出 a x a_{x} ax

为方便,这里的 l ≥ 1 , r ≥ 1 l \geq 1, r \geq 1 l1,r1。最后输出整个数组。

e . g . e.g. e.g. 目前 a = { 1 , 2 , 3 , 3 , 3 , 3 } a=\{1,2,3,3,3,3\} a={1,2,3,3,3,3},给 [ 3 , 5 ] [3,5] [3,5] 加上首项为 2 2 2 、公差为 1 1 1 的等差数列, a a a 变为 { 1 , 2 , 5 , 6 , 7 , 3 } \{1,2,5,6,7,3\} {1,2,5,6,7,3}

原来的 d = { 1 , 1 , 1 , 0 , 0 , 0 } d=\{1,1,1,0,0,0\} d={1,1,1,0,0,0},修改后的 d d d 数组: { 1 , 1 , 3 , 1 , 1 , − 4 } \{1,1,3,1,1,-4\} {1,1,3,1,1,4}。显然: a a a 区间加等差,相当于 d d d 区间加常数、端点单点修改

我们自然要把 d [ l + 1 , r ] d_{[l + 1,r]} d[l+1,r] 加上 w w w d l d_{l} dl 加上 f 1 f_{1} f1 ;那么右端点呢?要把刚才的操作复原 d r + 1 − = f 1 + ( r − l ) w d_{r+1} -= f_{1} + (r - l)w dr+1=f1+(rl)w

因此,我们要解决的问题在于,维护 d d d 数组,支持单点修改、区间加,可以使用线段树或者差分+树状数组(比如 d d d 的差分数组 d ′ = { 1 , 0 , 0 , − 1 , 0 , 0 } d' = \{1,0,0,-1,0,0\} d={1,0,0,1,0,0} ,修改后的 d ′ = { 1 , 0 , 2 , − 2 , 0 , − 5 } d' = \{1, 0, 2, -2, 0, -5\} d={1,0,2,2,0,5} ,对 a a a 加上等差数列,相当于对 d d d 单点修改端点、区间加等差 w w w ,相当于对 d ′ d' d 单点修改端点、单点修改区间端点)加以解决。

五、二维差分

二维差分不过是在一维差分之上,又加了一维而已。它的递推公式如下:
d [ i ] [ j ] = a [ i ] [ j ] − a [ i ] [ j − 1 ] − a [ i − 1 ] [ j ] + a [ i − 1 ] [ j − 1 ] d[i][j] = a[i][j] - a[i][j - 1] - a[i- 1][j] + a[i - 1][j - 1] d[i][j]=a[i][j]a[i][j1]a[i1][j]+a[i1][j1]

当前元素减去左边元素再减去上边元素再加上左上角元素

由二维差分数组求出原数组,只需要求出二维前缀和即可。这样一来,通过二维差分可以快速实现矩阵加减(二维区间加减)。

六、总结

这里介绍了简单的差分思想,还有区间加二次函数,多次差分,树上差分,有限微积分…

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

memcpy0

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值