所谓树链剖分就是把树上的路径转化为连续的区间从而用各种数据结构解决树上的问题。
而转化为区间则是利用其重链的性质把一条树上的路径变成很多连续的重链从而加速找两点的lca。
并在节点向上跳的过程中对区间用数据结构做更新操作,或是将连续的重链保存做离线操作。
常见的树链剖分有更新链或子树的权值并求其权值。权值也可以换成任何可以在连续区间用数据结构解决的其他询问。
一、
首先需要对树进行预处理,通过一次dfs找出每个节点的父亲节点、重儿子节点、以及节点下子树的大小、节点深度。
dep[M]:深度
f[M]:父亲
son[M]:重儿子
sz[M]:子树大小
void dfs(int u,int fa,int d){ sz[u]=1,dep[u]=d,f[u]=fa,son[u]=-1; for(int i=head[u];~i;i=e[i].next){ int v=e[i].v; if(v==fa) continue; dfs(v,u,d+1); sz[u]+=sz[v];//update size if(son[u]==-1||sz[v]>sz[son[u]]) son[u]=v;//子树大小最大的儿子是重儿子 } return ; } dfs(1,-1,1)进入
二、
由上一步预处理出的信息进而得到每条重链的起始节点为之后的剖分加速。以及处理树的dfs序把所有的子树和重链变为连续的区间。
top[M]:每条重链的起始节点,将重儿子连接起来的链称为重链。
rnk[M]:连续区间中的下标在树中的编号。
id[M]:树中节点在连续区间里的编号。
void dfs1(int u,int t){ id[u]=++tot;//dfs序 rnk[tot]=u; top[u]=t;//重链上的top都为链的起始节点 if(son[u]==-1) return ; dfs1(son[u],t);//先更新重儿子使得重链是连续的区间 for(int i=head[u];~i;i=e[i].next){ int v=e[i].v; if(v==f[u]||v==son[u]) continue; dfs1(v,v);//轻儿子是其重链的起始节点 } return ; } dfs1(1,1)进入
三、
将询问的的路径剖分为不同的重链即连续的区间。在连续的区间中可以用各种数据结构搞事情。
void update1(int x,int y,int v){ int fx=top[x],fy=top[y];//把节点指向其重链的起始节点 while(fx!=fy){//如果这两个点不在一条重链上则一直向上跳 if(dep[fx]>dep[fy]){//fx节点深度更深 update(id[fx],id[x],1,n,1,v); x=f[fx],fx=top[x];//从这条重链爬到父节点的重链上去 } else{//同理 update(id[fy],id[y],1,n,1,v); y=f[fy],fy=top[y]; } } if(dep[x]<dep[y])//在一条重链中时深度可能不相等 update(id[x],id[y],1,n,1,v); else update(id[y],id[x],1,n,1,v); } //路径的查询操作与更新操作基本一致。
四、
写好数据结构。
例题:
hdu 3966 Aragorn's Story 区间更新路径权值,单点查询权值。
洛谷 3384 区间更新+区间查询。
洛谷 2146 软件包管理器 支持链与子树的更新与查询
poj 2763 Housewife Wind&&fzu 2082 过路费 处理边权而非点权,边权可以映射到点权里去。
poj 3237 Tree 单点更新,区间取反,区间查询。
hdu 4718 The LCIS on the Tree 树链剖分+求区间LCIS
hdu 5052 Yaoge’s maximum profit 树链剖分+线段树合并
hdu 5029 Relief grain 树链剖分+权值线段树