利用差分和前缀和来优化代码,可避免超时。差分与前缀和是一对互逆的操作,常用于求解区间问题。
1.差分主要用于多次对区间进行加减操作的问题
差分算法涉及的主要操作陈述如下:
(1)构建差分数组
【差分数组概念】设原数组为含有 n 个数的 a 数组(下标常从 1 开始),差分数组为 d 数组(下标常从 1 开始)。其中,令 d[1]=a[1],且对于 i∈[2,n],令 d[i]=a[i]-a[i-1]。
【示例】例如,设原数组 a[]=[3 5 9 10 16 18 27] (下标常从 1 开始),则有:
d[1]=a[1]=3,
d[2]=a[2]-a[1]=5-3=2,
d[3]=a[3]-a[2]=9-5=4,
d[4]=a[4]-a[3]=10-9=1,
d[5]=a[5]-a[4]=16-10=6,
d[6]=a[6]-a[5]=18-16=2,
d[7]=a[7]-a[6]=27-18=9,
即差分数组为 d[]=[3 2 4 1 6 2 9]。
(2)对原数组的区间 [le, ri] 进行加减操作
【结论】构造差分数组后,对原数组的区间 [le, ri] 的加减操作就转化为对差分数组的区间端点的操作:d[le]+=x,d[ri+1]-=x。显然,这大大降低了算法的时间复杂度。
【示例】例如,对原数组 a[]=[3 5 9 10 16 18 27] (下标常从 1 开始)的区间 [2,5] 中的每个数都加 3,则等价于执行d[2]+=3,d[5+1]-=3。
【解析】现在,来分析一下对原数组 a[]=[3 5 9 10 16 18 27] (下标常从 1 开始)执行 d[2]+=3,d[5+1]-=3 后,是否等价于对原数组的区间 [2,5] 中的每个数都加了 3?
由上文分析知,原数组 a[]=[3 5 9 10 16 18 27] 的差分数组为 d[]=[3 2 4 1 6 2 9]。显然,执行 d[2]+=3,d[5+1]-=3 后,原数组的差分数组由 d[]=[3 2 4 1 6 2 9] 变为 d[]=[3 5 4 1 6 -1 9]。
显然,由关系 d[1]=a[1],d[i]=a[i]-a[i-1] (i≥2),知:
a[1]=d[1]=3
a[2]=d[2]+a[1]=5+3=8
a[3]=d[3]+a[2]=4+8=12
a[4]=d[4]+a[3]=1+12=13
a[5]=d[5]+a[4]=6+13=19
a[6]=d[6]+a[5]=-1+19=18
a[7]=d[7]+a[6]=9+18=27
可见,对原数组 a[]=[3 5 9 10 16 18 27] (下标常从 1 开始)执行 d[2]+=3,d[5+1]-=3 后,原数组变为 a[]=[3 8 12 13 19 18 27],原数组区间 [2,5] 内的每个元素确实都加了3。
(3)对原数组的多个区间进行加减操作
【示例】例如,对原数组 a[]=[3 5 9 10 16 18 27] (下标常从 1 开始)的区间 [2,5] 中的每个数都加 3,则等价于执行d[2]+=3,d[5+1]-=3。然后,对区间 [3,6] 中的每个数都减 2(相当于加 -2),则等价于执行 d[3]+=(-2),d[6+1]-=(-2)。最后,对区间 [1,4] 中的每个数都加 5,则等价于执行d[1]+=5,d[4+1]-=5。
【解析】由上文分析知,原数组 a[]=[3 5 9 10 16 18 27] 的差分数组为 d[]=[3 2 4 1 6 2 9]。
执行 d[2]+=3,d[5+1]-=3 后,由 d[]=[3 2 4 1 6 2 9] 变为 d[]=[3 5 4 1 6 -1 9]。
执行 d[3]+=(-2),d[6+1]-=(-2) 后,由 d[]=[3 5 4 1 6 -1 9] 变为 d[]=[3 5 2 1 6 -1 11]。
执行 d[1]+=5,d[4+1]-=5 后,由 d[]=[3 5 2 1 6 -1 11] 变为 d[]=[8 5 2 1 1 -1 11]。
然后,仅观察最后生成的差分数组 d[]=[8 5 2 1 1 -1 11],且依据关系 d[1]=a[1],d[i]=a[i]-a[i-1] (i≥2),知:
a[1]=d[1]=8
a[2]=d[2]+a[1]=5+8=13
a[3]=d[3]+a[2]=2+13=15
a[4]=d[4]+a[3]=1+15=16
a[5]=d[5]+a[4]=1+16=17
a[6]=d[6]+a[5]=-1+17=16
a[7]=d[7]+a[6]=11+16=27
即,经过多轮对原数组的差分数组端点进行操作后,原数组 a[]=[8 13 15 16 17 16 27]。
为了验证比较,现在按上文对原数组 a[]=[3 5 9 10 16 18 27] 给定的区间进行操作的内容如下:
对区间 [2,5] 中的每个数都加 3,由 a[]=[8 13 15 16 17 16 27] 变为 a[]=[3 8 12 13 19 18 27]。
对区间 [3,6] 中的每个数都减 2,由 a[]=[3 8 12 13 19 18 27] 变为 a[]=[3 8 10 11 17 16 27]。
对区间 [1,4] 中的每个数都加 5,由 a[]=[3 8 10 11 17 16 27] 变为 a[]=[8 13 15 16 17 16 27]。
显然,与利用差分数组算出来的结果一样。
可见,若给定的原数组序列特别长,则差分数组对端点的操作将比原数组对区间的操作在得到计算结果上快得多,虽然它俩在计算结果上是一样的。
2.前缀和主要用于多次对区间进行求和的问题
前缀和,无论是一维前缀和还是二维前缀和,通常都用于快速对区间进行求和。 它们的思想都是利用给定的数据预先处理出一个前缀和数组 sum[],之后在计算区间和的时候查表,从而可以大大降低算法的时间复杂度。参见:https://blog.csdn.net/hnjzsyjyj/article/details/120265452
一维前缀和数组预处理过程:sum[i]=sum[i-1]+a[i]
一维区间和计算过程:sum[y]-sum[x-1] (y≥x)
二维前缀和数组预处理过程:sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+a[i][j]
二维区间和计算过程:sum[x2][y2]-sum[x1-1][y2]-sum[x2][y1-1]+sum[x1-1][y1-1] (y2≥y1,x2≥x1)
3.在连续区间上应用差分算法的常见步骤:差分数组 → 原数组 → 前缀和
(1)对连续区间 [le,ri] 利用“d[le]+=1; d[ri+1]-=1;”构造差分数组d[];
(2)利用“d[1]=a[1],且对于 i∈[2,n],d[i]=a[i]-a[i-1]”构造原数组a[];
(3)利用“sum[i]=sum[i-1]+a[i]”构造前缀和数组sum[]。
示例详见:https://blog.csdn.net/hnjzsyjyj/article/details/132238683
在连续区间上应用差分算法的常见步骤:差分数组 → 原数组 → 前缀和
猜你喜欢
转载自blog.csdn.net/hnjzsyjyj/article/details/132247279
今日推荐
周排行