神奇的差分法(内附树状数组的一点扩展)

版权声明: https://blog.csdn.net/zhangjianjunab/article/details/83753002

差分法是我们所用的一个强力的武器!

有这把武器你就可以统治世界。。。

一个大佬曾经讲过,一但碰到区间修改的题,就要优先考虑差分。

目录

  1. 普通差分法
  2. 差分套差分(二阶差分)
  3. 高阶差分
  4. 树上差分(点的意义与边的意义)
  5. 例题

普通差分法

我们有时做题,会发现这么一种题。

给你长度为n的序列,m次操作,有两种:1. 让[l,r]区间加上k。2. 查询一个点的值。

典型的区间修改,单点查询。
单点查询简单。
但是区间修改怎么做?
暴力卡常。。。
线段树。。。
比较正经的做法是差分,差分是什么?

在这里插入图片描述

对于一个序列a,定义另一个序列b, b [ i ] = a [ i ] a [ i 1 ] b[i]=a[i]-a[i-1] ,则叫b数组为a数组的差分数组,这样有什么优势呢?

首先,如果要求a[i],我们会发现就是 b [ i ] + a [ i 1 ] b[i]+a[i-1] ,不断拆开, a [ i ] = b [ i ] + b [ i 1 ] + b [ i 2 ] + b [ i 3 ] + . . . . + b [ 1 ] a[i]=b[i]+b[i-1]+b[i-2]+b[i-3]+....+b[1] ,是不是很棒棒,就是前缀和!

但是单点查询O(n)呀

先讲这样怎么修改,假设是 [ l , r ] [l,r] 区间加上k,那么我们就让 b [ l ] b[l] 加上 k k ,让 b [ r + 1 ] b[r+1] 减去 k k 就行了。这样,在 [ l , r ] [l,r] 区间内,每个数都会加上 b [ l ] b[l] 多出的 k k ,但是在 r r 之后,我们也会因为 b [ r + 1 ] b[r+1] 减去了 k k 从而和 b [ l ] b[l] k k 抵消。

这样,区间修改就完成了!

但是单点查询又复杂了,它可以表达成前缀和,前缀和…树状数组维护就可以了!
是不是很不错!

这里给大家一点扩展!

至于树状数组区间查询,区间修改,我粘上一个大佬的话,我认为很不错!


我们还是需要引入delta数组,这里的delta[i]表示区间a[i…j]都需要加上的值的和。那么当我们需要将区间[l,r]上的每个数都加上x时,我们还是可以直接在树状数组上将delta[l]加上x,delta[r+1]减去x。

那么问题来了,如何查询区间[l,r]的和?

我们设a[1…i]的和为sum[i],根据delta数组的定义,则:

s u m [ i ] = j = 1 i a [ j ] + j = 1 i d e l t a [ j ] ( i j + 1 ) sum[i]=\sum_{j=1}^ia[j]+\sum_{j=1}^idelta[j]*(i-j+1)
s u m [ i ] = j = 1 i a [ j ] + ( i + 1 ) j = 1 i d e l t a [ j ] j = 1 i d e l t a [ j ] j sum[i]=\sum_{j=1}^ia[j]+(i+1)*\sum_{j=1}^idelta[j]-\sum_{j=1}^idelta[j]*j

这样我们就不难看sum[i]是由哪三个部分组成的了。我们需要用一个asum数组维护a数组的前缀和,delta1与delta2两个树状数组,delta1维护delta数组的和,delta2维护delta[i]*i的和,代码如下:

void add(int *arr int pos,int x){
    while(pos<=n) arr[pos]+=x,pos+=lowbit(pos);
}
void modify(int l,int r,int x){
    add(d1,l,x),add(d1,r+1,-x),add(d2,l,x*l),add(d2,r+1,-x*(r+1));
}
int getsum(int *arr,int pos){
    int sum=0;
    while(pos) sum+=arr[pos],pos-=lowbit(pos);
    return sum;
}
int query(int l,int r){
    return asum[r]+r*getsum(d1,r)-getsum(d2,r)-(asum[l-1]+l*getsum(d1,l-1)-getsum(d2,l-1));
}

摘自


咳咳,回归正题,总结一下
普通差分就是这个数减去前一个数所得到的一个数组,他不是个算法,只是种技巧,比如在树状数组中的妙用,让树状数组具有区间查询,单点修改的功能。

差分套差分(二阶差分)

没错你没有听错,差分都可以套了!

好像又叫二阶差分。

怎么套?将差分数组再差分一遍,求到了差分套差分的数组,定位c数组。

那么,推一推,发现 a [ i ] = c [ i ] 1 + c [ i 1 ] 2 + . . . + c [ 1 ] ( i 1 ) a[i]=c[i]*1+c[i-1]*2+...+c[1]*(i-1)

嗯,这个有什么用呢?

如果有一个毒瘤出题人,出了一道题(就是我被坑了,就写出来了):

给你一个长度为n的序列a,有m次操作,每次操作让区间[l,r]分别加上t,t*2,t*3,...,t*(r-l+1)
最后输出a序列的每个数的值

把1操作中加上的数差分,就为 t , t , t , t , t , . . . , t t,t,t,t,t,...,t (注意:以后求高阶差分的修改公式,将加上的数组也进行差分来推是最好的!),那么,就等于给a的差分数组b区间加上t,那么就将b再差分出另一个差分数组c来更改,最后O(n)输出一下答案就好了。

当然,相比差分,差分套差分会有更多应用,欢迎大家探究!

高阶差分

这个就很毒瘤了,一般没有人出这种题。讲一下就是希望以后有什么人出这种毒瘤题

我们通过列三阶差分,设数组为d,则有 a [ i ] = 1 d [ i ] + 3 d [ i 1 ] + 6 d [ i 2 ] + 10 d [ i 3 ] + . . . . . . a[i]=1*d[i]+3*d[i-1]+6*d[i-2]+10*d[i-3]+...(猴子的脑子烧焦了...)
我们观察到 3 1 = 2 , 6 3 = 3 , 10 6 = 4... 3-1=2,6-3=3,10-6=4... 这不是二阶等差数列吗?

继续观察:

我们发现n阶差分数组单点查询就等于n-1阶差分数组的前缀和,也就是说,而我们发现从三阶开始, d [ i ] = c [ i ] + c [ i 1 ] + c [ i 2 ] . . + c [ 1 ] d[i]=c[i]+c[i-1]+c[i-2]..+c[1] ,而 c [ i ] = 1 a [ i ] + 2 a [ i 1 ] + . . . + ( i 1 ) a [ 1 ] c[i]=1*a[i]+2*a[i-1]+...+(i-1)*a[1] ,也就是等差数列,我们将 c [ i ] c [ i 1 ] c [ i 2 ] . . . c[i]、c[i-1]、c[i-2]... 逐渐拆开,会发现 a [ j ] ( j &lt; = i ) a[j](j&lt;=i) 的系数会在 c [ i ] , c [ i 1 ] , . . . , c [ j ] c[i],c[i-1],...,c[j] 中加上 1 , 2 , 3 , . . . , ( i j + 1 ) 1,2,3,...,(i-j+1) 而a[j+1]的系数减去a[j]的系数就是(j-i+1),所以从i到1的系数不就是个二阶等差数列吗?

继续推下去,我们发现n阶差分从i到1的系数就是 ( n 1 ) (n-1) 阶的等差数列。

PS:n阶等差数列:相邻两两数之间的差是 ( n 1 ) (n-1) 等差数列的数列,0阶数列就是一个常数序列。
如:1 4 9 16 25 36,减完后为3 5 7 9 11,则1 4 9 16 25 36是二阶等差数列

注意,我们把 ( n 1 ) (n-1) 阶的等差数列之间每个数的差当成一个数列取出来,这个数列就是 ( n 2 ) (n-2) 阶的等差数列,比如: 1 , 3 , 6 , 10 1,3,6,10 将两两差取出,就是 1 , 2 , 3 , 4 1,2,3,4 一个一阶等差数列。(1是由 1 0 1-0 得出来的)那么,不断的进行操作,我们最后会将这个 ( n 1 ) (n-1) 阶的等差数列得到一个首项为1,等差为1的等差数列,这是比较重要的。

建议跳过的自己乱搞的东西。。。


至于等差数列求第i项公式,表示并不会,网上也没有找到,但是预处理一遍不好吗?就是你不会公示吧!

如果有哪位大佬会的话,麻烦发一下评论,我能修改一下我的博客,如果只会二阶的求前i项和的公式,也请发出来,谢谢!

先讲讲我捞比了两个小时的成果。

先发出来二阶等差数列求第i项的公式:
b [ i ] = b [ 1 ] + a [ 1 ] ( i 1 ) + t ( ( i 1 ) ( i 2 ) / 2 ) b [ 1 ] a [ 1 ] t b[i]=b[1]+a[1]*(i-1)+t*((i-1)*(i-2)/2),b[1]为二阶等差数列首项,a[1]与t是原数列的一阶等差数列的首项与公差
三阶的公式:
c [ 1 ] + b [ 1 ] ( i 1 ) + a [ 1 ] ( 1 + 2 + . . . + ( i 2 ) ) + t ( 1 + 3 + 6 + . . . + ( i 2 ) ( i 3 ) / 2 ) c[1]+b[1]*(i-1)+a[1]*(1+2+...+(i-2))+t*(1+3+6+...+(i-2)*(i-3)/2) ,变量意义与之前的意思差不多
我们发现,系数从左到右,第一个乘1,第二个是0阶等差数列前i-1项和 ( 1 , 1 , 1 , 1 , 1 , 1... ) (1,1,1,1,1,1...) ,第二个是1阶等差数列前i-2项和 ( 1 , 2 , 3 , 4 , . . . ) (1,2,3,4,...) ,第三个是2阶等差数列前i-3项和 ( 1 , 3 , 6 , 10... ) (1,3,6,10...) ,所以我们可以大胆假设对于n阶等差数列求第i项值 ( i &gt; = n ) (i&gt;=n) 则为:

a [ n ] [ i ] = a [ n ] [ 1 ] 1 + a [ n 1 ] [ 1 ] ( i 1 ) + a [ n 2 ] [ 1 ] ( 1 + 2 + 3 + . . . ) + a [ n 3 ] [ 1 ] ( 1 + 3 + 6 + 10... ) + . . . + t ( n 1 i n ) a[n][i]=a[n][1]*1+a[n-1][1]*(i-1)+a[n-2][1]*(1+2+3+...)+a[n-3][1]*(1+3+6+10...)+...+t*(n-1阶等差数列前i-n项和)

注意:这里系数所求的 j ( j &lt; n ) j(j&lt;n) 阶等差数列的和中,由 j j 阶等差数列推导出的0阶等差数列是 ( 1 , 1 , 1 , 1 , 1... ) (1,1,1,1,1...) ,而且推导出的 k ( k &lt; = j ) k(k&lt;=j) 阶的等差数列中首项都是1。

以上就是我瞎搞的成果了。。。
大佬:语言不通畅,还垃圾。
蒟蒻:这根本不是人看的。
我:我也看不懂我写了什么。。。
所有人:没图,差评。


树上等差数列

基本概念:

  1. 树上两点之间只有一条最短路径
  2. 树上两点只有一个最近公共祖先

1. 点差分

点差分求什么?

给你一棵树,并给你一些在树上的路径,让你求每个点在树上被经过的次数。

整篇博客没一张图。。。
在这里插入图片描述
图中红色绿色蓝色代表三条路径,点旁边的标记代表他被经过的次数。

如何求?
暴力!
在这里插入图片描述

。。。
DFS暴力的话,肯定过不了呀!如果你送毒瘤出题人足够刀片说不定可以。。。

这时候,就有人跳出来发明了个算法,叫树上差分:
设路径的开头与结尾为 s t st e d ed ,设 k = l c a ( s t , e d ) k=lca(st,ed) f a t h e r k father_{k} k k f a t h e r father
那么我们把 f [ s t ] + + , f [ e d ] + + , f [ k ] , f [ f a t h e r k ] f[st]++,f[ed]++,f[k]--,f[father_{k}]-- ,有什么用?
在这里插入图片描述

我们跑用DFS遍历一遍一棵树,设 t o t i tot_{i} i i 的子树的所有节点的 f f 和,如图:在 s t &gt; e d st-&gt;ed 这条路径中,除了 e d ed 外, t o t tot 值都是 1 1 ,又因为 k k 点的 f f 值为 1 -1 ,所以将一个1消掉了,所以 k k 的tot值也为1.

某银:那 k k 的父亲呢?

因为我们让 f a t h e r k father_{k} f 1 f值也减了1 ,所以他和 k k 的1抵消了,所以并没有影响。

所以我们只需要 O ( 1 ) O(1) 将所有路径处理完, O ( n ) O(n) 遍历处理答案就好了!

2. 边差分

跟点差分差不多。

给你一棵树,并给你一些在树上的路径,让你求每条边在树上被经过的次数。

在这里插入图片描述

首先,我们应当考虑把边压到点里面,那么我们就让每个点到父亲的那条边压到这个点身上,然后求每个点被经过的次数就行了,点差分一下,有什么难?

恭喜你WA了。
在这里插入图片描述

难道你就没有发现只算一条路径的话, k k 到父亲这条边没有被经过,但是在点差分过程中 t o t k = 1 tot_{k}=1 吗?所以,我们应当改一下修改 f f 值的过程。

f [ s t ] + + , f [ e d ] + + , f [ k ] = 2 f[st]++,f[ed]++,f[k]-=2

那么在 k k 点的时候,就把 s t e d st、ed 的影响消掉了,是不是很舒服?

最后DFS一遍,别忘了每个点代表的是他到父亲的边!

那么不就解决了?

至此,基础的差分结束了。

终于写完了,ヾ(≧▽≦*)o,<( ̄ˇ ̄)/,~( ̄▽ ̄~)(~ ̄▽ ̄)~,(:逃

欢迎大家D我,让我能更好的完善博客!

猜你喜欢

转载自blog.csdn.net/zhangjianjunab/article/details/83753002