浅谈树链剖分
Posted wr-eternity
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了浅谈树链剖分相关的知识,希望对你有一定的参考价值。
今天刚学会树剖。。。。。。(是不是觉得我很菜QwQ)
树剖的用处:
引子问题1:
给你一颗树,支持两种操作:
1、给x到y路径上的值加z
2、求出点x的值
简单,树上差分嘛,前几天刚学过啊。
引子问题2:
给你一颗树,支持两种操作:
1、给以x为根的子树加z
2、求出以x为根的子树的和。
简单,dfs序+线段树啊。
那么把两个问题结合起来呢?——树链剖分华丽丽登场!!!
树剖核心思想:
听说线段树挺好用的,区改区查只要log的复杂度,但是只能在线性结构上用,哎,真是太遗憾了。
听说有一种叫做dfs的东西可以把一棵树转化为线性结构。
深夜,某dalao拿着这两个东西搞来搞去然后——树链剖分就诞生啦!(好吧是我瞎yy的。。。)
没错,树剖就是把一棵树分成很多条链,使这些链的dfs序连续,从而把维护线性数据的数据结构搬到树上。
关于重链和轻链:
把树分成很多条链,那么应该怎么分呢?树剖发明人把树分为了重链和轻链,重链在dfs中的编号是连续的,也就是说我们可以一次直接从重链的顶端跳到重链的顶端,期间只要用个线段树维护一下就可以了,所以就非常快,诶,这就很优秀,但是轻链的话就只能一个一个慢慢跳,诶,很烦。
那么他是如何确定哪些链是重链,哪些链是轻链的呢?
对于树上的一个节点u,取其子树大小最大的儿子作为他的重儿子,那么重儿子和u的连边就是重边,由重边组成的链就是重链。
这个时候我们可能产生一个疑问:
不是说重链跳得快吗,那为什么不取子树深度最大的作为中儿子而要取子树规模最大的呢?
这个问题就问的非常好,很有深度,不经过一定的思考是问不来这个问题的。
那么这是为什么呢?
很简单啊其实,注意到轻链只能一个一个跳,非常的凄惨,很慢,那么如果我们取的是子树规模较大的儿子为重儿子,那么一个轻节点就可以少跳几步就跳到一条重链上,然后搭个“顺风车”,诶,这就可以加速很多,而如果取子树深度较大的儿子为重儿子,那么轻节点可能就要多条几次才能跳到,而又优化的只是重链上的节点而已,对于大局而言这就很亏,诶,很不划算。(其实这是我实测出来的。。。)
树剖主体代码实现:
procedure dfs1(u,father,dep:longint); var i,v:longint; begin depth[u]:=dep; siz[u]:=1; //depth:深度,siz:子树规模 i:=head[u]; faz[u]:=father; //faz:父亲 while i<>0 do begin v:=vet[i]; if v<>father then begin dfs1(v,u,dep+1); if siz[v]>siz[son[u]] then son[u]:=v; //son:重儿子是哪个 siz[u]:=siz[u]+siz[v]; end; i:=next[i]; end; end; procedure dfs2(u,father,t:longint); var i,v:longint; begin inc(time); i:=head[u]; //dfn:dfs序,top:链顶 dfn[u]:=time; top[u]:=t; if son[u]=0 then exit; dfs2(son[u],u,t); //这里先dfs遍历重儿子是为了让重链连续 while i<>0 do begin v:=vet[i]; if (v<>father)and(v<>son[u]) then dfs2(v,u,v); i:=next[i]; end; end;
更新、查询操作:
更新的时候要把深度大的往上跳,避免出现擦肩而过的尴尬情况。
procedure update_path(x,y,z:longint); var fx,fy:longint; begin fx:=top[x]; fy:=top[y]; while fx<>fy do begin if depth[fx]>depth[fy] then //选深度大的往上跳 begin update(1,1,time,dfn[fx],dfn[x],z); //更新链上的值 x:=faz[fx]; //可以一下跳到链顶,轻链的链顶就是它自己 end else begin update(1,1,time,dfn[fy],dfn[y],z); //这里的update和下面的query是线段树的更新和查询操作 y:=faz[fy]; end; fx:=top[x]; fy:=top[y]; end; if x<>y then //感觉这句话没事么用处。。。。。。 if dfn[x]>dfn[y] then update(1,1,time,dfn[y],dfn[x],z) else update(1,1,time,dfn[x],dfn[y],z) else update(1,1,time,dfn[x],dfn[y],z); end; function query_path(x,y:longint):longint; var fx,fy:longint; begin fx:=top[x]; fy:=top[y]; query_path:=0; while fx<>fy do begin if depth[fx]>depth[fy] then begin query_path:=(query_path+query(1,1,time,dfn[fx],dfn[x]))mod p; x:=faz[fx]; end else begin query_path:=(query_path+query(1,1,time,dfn[fy],dfn[y]))mod p; y:=faz[fy]; end; fx:=top[x]; fy:=top[y]; end; if x<>y then if dfn[x]>dfn[y] then query_path:=(query_path+query(1,1,time,dfn[y],dfn[x]))mod p else query_path:=(query_path+query(1,1,time,dfn[x],dfn[y]))mod p else query_path:=(query_path+query(1,1,time,dfn[x],dfn[y]))mod p; end;
树剖模板代码实现:
题目为洛谷P3384。
var dfn,fin,faz,siz,son,head,depth,top,a:array[0..100000]of longint; add_sum,sum:array[0..400000]of longint; next,vet:array[0..200000]of longint; i,n,m,root,p,x,y,z,time,tot,opt,q:longint; function min(a,b:longint):longint; begin if a<b then exit(a) else exit(b); end; function max(a,b:longint):longint; begin if a>b then exit(a) else exit(b); end; procedure add_(x,y:longint); begin inc(tot); next[tot]:=head[x]; vet[tot]:=y; head[x]:=tot; end; procedure update(k,l,r,x,y,z:longint); var mid:longint; begin if (l>=x)and(r<=y) then begin add_sum[k]:=(add_sum[k]+z)mod p; exit; end; sum[k]:=(sum[k]+(min(r,y)-max(l,x)+1)*z mod p)mod p; mid:=(l+r)>>1; if x<=mid then update(k*2,l,mid,x,y,z); if y>mid then update(k*2+1,mid+1,r,x,y,z); end; function query(k,l,r,x,y:longint):longint; var mid:longint; begin if (l>=x)and(r<=y) then exit((sum[k]+(r-l+1)*add_sum[k]mod p)mod p); mid:=(l+r)>>1; query:=(min(r,y)-max(l,x)+1)*add_sum[k]mod p; if x<=mid then query:=(query+query(k*2,l,mid,x,y))mod p; if y>mid then query:=(query+query(k*2+1,mid+1,r,x,y))mod p; end; procedure dfs1(u,father,dep:longint); var i,v:longint; begin depth[u]:=dep; siz[u]:=1; i:=head[u]; faz[u]:=father; while i<>0 do begin v:=vet[i]; if v<>father then begin dfs1(v,u,dep+1); if siz[v]>siz[son[u]] then son[u]:=v; siz[u]:=siz[u]+siz[v]; end; i:=next[i]; end; end; procedure dfs2(u,father,t:longint); var i,v:longint; begin inc(time); i:=head[u]; dfn[u]:=time; top[u]:=t; fin[u]:=dfn[u]+siz[u]-1; if son[u]=0 then exit; dfs2(son[u],u,t); while i<>0 do begin v:=vet[i]; if (v<>father)and(v<>son[u]) then dfs2(v,u,v); i:=next[i]; end; end; procedure update_path(x,y,z:longint); var fx,fy:longint; begin fx:=top[x]; fy:=top[y]; while fx<>fy do begin if depth[fx]>depth[fy] then begin update(1,1,time,dfn[fx],dfn[x],z); x:=faz[fx]; end else begin update(1,1,time,dfn[fy],dfn[y],z); y:=faz[fy]; end; fx:=top[x]; fy:=top[y]; end; if x<>y then if dfn[x]>dfn[y] then update(1,1,time,dfn[y],dfn[x],z) else update(1,1,time,dfn[x],dfn[y],z) else update(1,1,time,dfn[x],dfn[y],z); end; function query_path(x,y:longint):longint; var fx,fy:longint; begin fx:=top[x]; fy:=top[y]; query_path:=0; while fx<>fy do begin if depth[fx]>depth[fy] then begin query_path:=(query_path+query(1,1,time,dfn[fx],dfn[x]))mod p; x:=faz[fx]; end else begin query_path:=(query_path+query(1,1,time,dfn[fy],dfn[y]))mod p; y:=faz[fy]; end; fx:=top[x]; fy:=top[y]; end; if x<>y then if dfn[x]>dfn[y] then query_path:=(query_path+query(1,1,time,dfn[y],dfn[x]))mod p else query_path:=(query_path+query(1,1,time,dfn[x],dfn[y]))mod p else query_path:=(query_path+query(1,1,time,dfn[x],dfn[y]))mod p; end; begin read(n,q,root,p); for i:=1 to n do read(a[i]); for i:=1 to n-1 do begin read(x,y); add_(x,y); add_(y,x); end; dfs1(root,0,1); dfs2(root,0,root); for i:=1 to n do update(1,1,time,dfn[i],dfn[i],a[i]); while q>0 do begin read(opt); if opt=1 then begin read(x,y,z); update_path(x,y,z); end; if opt=2 then begin read(x,y); writeln(query_path(x,y)); end; if opt=3 then begin read(x,z); update(1,1,time,dfn[x],fin[x],z); end; if opt=4 then begin read(x); writeln(query(1,1,time,dfn[x],fin[x])); end; dec(q); end; end.
以上是关于浅谈树链剖分的主要内容,如果未能解决你的问题,请参考以下文章