区间加等差数列

算法笔记

顾名思义,就是在[l,r][l, r]区间上加上首项为kk,公差为dd的等差数列。分为以下这么三类情况:

先加后询问

即先加,最后再输出整个数组,或者询问的位置和修改的左端点是有序的。这种情况我们不需要借助其他数据结构,有以下两种思路:

靠思维

我们先回顾一下区间加同样的数是怎么做的,我们维护当前加在原数组上的值,并用一个数组addadd标记维护的值在ii位置该如何变化,比如说给[l,r][l, r]加上xx,我们令a[l] += x, a[r+1] -= x,这样[l,r][l, r]中的数都会加xx,然后r1r-1位置还原。

加等差数列就是上述思路的扩展,我们不仅维护当前加在原数组上的值curcur,还维护当前的公差incinc,以及用两个数组cur_addcur\_addinc_addinc\_add标记两个维护的值的变化。当加一个等差数列时,我们在ll处标记dd代表从ll处开始公差加dd,在r+1r+1处标记d-d以复原公差。curcur也需要类似的标记:在ll处标记kk,在r+1r+1处标记(k+(rl+1)d)-(k+(r-l+1)\cdot d)。总结一下:

auto add = [&](int l, int r, int k, int d) {  
    cur_add[l] += k;
    cur_add[r+1] -= k + (r - l + 1) * d;
 
    inc_add[l] += d;
    inc_add[r+1] -= d;
}
 
auto update_current_position = [&](int i) {
    cur += cur_add[i];
    inc += inc_add[i];
    cur += inc;
}

靠数学

分析一下差分数组

下标l1l-1lll+1l+1l+2l+2\dotsrrr+1r+1r+2r+2
原数组00kkk+dk+dk+2dk+2dk+(len1)dk+(len-1)d0000
一阶差分00kkdddddd(k+(len1)d)-(k+(len-1)d)00
二阶差分00kkdkd-k0000(k+lend)-(k+len\cdot d)k+(len1)dk+(len-1)d

不难看出二阶差分数组上产生了四次单点修改。所以我们先在二阶差分数组上修改最后再跑两次前缀和即可得到最后的数组。

放个例题CF1661D

区间修改,单点查询

此时我们就需要数据结构维护差分数组了

维护一阶差分数组

观察上面一阶差分可以发现有两个单点修改一个区间修改,aia_i是一阶差分的前缀和,所以用区间修改区间查询的数据结构即可(线段树,树状数组)

维护二阶差分数组

我们需要单点修改然后二阶前缀和,二阶前缀和可以拆成两个前缀和:

bi=1iai,ci=1ibib_i=\sum_1^ia_i, c_i = \sum_1^ib_i ci=ia1+(i1)a2++2ai1+ai =(i+1)(a1+a2++ai)(a1+2a2++iai)\begin{aligned} c_i &= i a_1 + (i-1)a_2 + \dots+2a_{i-1}+a_i\\\ &= (i+1)(a_1+a_2+\dots+a_i) - (a_1+2a_2+\dots+ia_i) \end{aligned}

维护aia_iiaiia_i的前缀和即可。

例题:洛谷 P1438 无聊的数列

区间修改,区间查询

要是搞三阶前缀和那可能略显麻烦了,我们不妨转变思路用线段树,其实与正常的区间加类似,懒惰标记变成了这个区间加的等差数列的首项和公差。