本篇参考 【顾z】dalao的讲解。
差分数组:
现讲一下差分数组的定义吧。
a[i]=Σnj=1 b[j]。
几个栗子:
原数组1,3,5,2,4。
差分数组1,2,2,-3,2。
那么这个数组有什么优势呢?
如果我们要在原数组中修改一段区间的值,假如是给l到r这段区间加上x,那么它在差分数组中的体现就是:b[l]+x,b[r+1]-x。
也就是说:我们把区间修改变成了单点修改。
例题:AT2442(洛谷上直接搜就好),洛谷P3948,P3943。其中P3943的题解戳这里。(难度由低到高排序,在这里再次感谢顾z dalao)
树上差分:
树上差分这个东西一般被用来解决树上的路径计数问题。
点差分:
那么当我们要修改7到8这一条链上的值,给它每个点加上1,我们应该怎么做呢?
对于一颗树(描红的点是要加的):
先给大家说一种非常简单的差分数组理解方式吧:
cnt[u]表示给根到u结点的路径经过的所有点都加上cnt[u]。
我们只需要在差分数组的7号点加1,8号点加1,他们的lca 4号点-1,lca的父亲2号点-1,就行了。
为什么呢?
cnt[7]+1:我们把1,2,4,7四个节点加上了1。
cnt[8]+1:我们把1,2,4,6,8五个节点加上了1。
注意到此时由根(1)到lca的父亲(2)的路径上所有点都多加了两遍1,而lca被多加了一次,所以:
cnt[4]-1:我们把1,2,4三个点都减了1。
cnt[2]-1:我们把1,2这两点减了1。
对于查询,在dfs时把以它为根的子树中差分数组的值都累加起来就是它当前的值了(就相当于是把差分数组搬到树上了嘛)。
得出,点差分:
cnt[u]++ cnt[v]++
cnt[lca]-- cnt[father[lca]]--
具体代码:
procedure getans(u,father:longint); var i,v:longint; begin g[u]:=cnt[u]; i:=head[u]; while i<>0 do
begin v:=vet[i]; if v<>father then begin getans(v,u); g[u]:=g[u]+g[v]; end; i:=next[i]; end; if g[u]>ans then ans:=g[u]; end; begin for i:=1 to m do begin
read(x,y); z:=lca(x,y); inc(cnt[x]); inc(cnt[y]); dec(cnt[z]); dec(cnt[f[z,0]]); end; getans(1,0); //答案统计再g数组里。
end.
边差分:
类似的,我们要给u到v这一条链上所有边都加1,也是差不多的。
还是那张图(描红的边是要加的):
我们把边压到点里(压到下面的那个点)。
我们只需要在差分数组的7号点加1,8号点加1,他们的lca 4号点-2就行了。
为什么呢?
cnt[7]+1:我们把1到2,2到4,4到7这三条边都加1。
cnt[8]+1:我们把1到2,2到4,4到6,6到8这四条都加1。
注意到此时根(1)到它们的lca(4)的路径上所有的边都被多加了2次,所以:
cnt[4]-2:我们把1到2,2到4这两条边都减2。
得出边差分:
cnt[u]++ cnt[v]++
cnt[lca]-2
很简单,不是吗?
具体代码:
procedure getans(u,father:longint); var i,v:longint; begin g[u]:=cnt[u]; i:=head[u]; while i<>0 do begin v:=vet[i]; if v<>father then begin getans(v,u); g[u]:=g[u]+g[v]; if (g[v]=num)and(dist[i]>=k) then flag:=true; end; i:=next[i]; end; end; begin for i:=1 to m do begin read(u[i],v[i]); grand[i]:=lca(u[i],v[i]); len[i]:=d[u[i]]+d[v[i]]-2*d[grand[i]]; if len[i]>max then max:=len[i];
end; getans(1,0); //答案存再g数组里。 end;
例题:洛谷P3128,P3258,P2680。其中P2680的题解戳这里。(难度按由低到高排序,再次感谢顾z dalao——我保证这是最后一次了【滑稽】)