潇爷的讲解。。十分的nice,可惜人傻。。。然而YveH似乎很早就A过一道题。。。。现在开始学。。。
树链剖分就是兹辞把一棵树上的信息,切成多条链,再把这些链hash到某数据上,是之兹辞对原树上的信息进行快速的查询与修改。
把树上的路径分类为重链和轻链PS此处为链剖线段树QwQ
定义: 记size[v]表示以v为根的子树的节点数,deep[v]表示v的深度(根深度为1),top[v]表示v所在的链的顶端节点,fa[v]表示v的父亲,son[v]表示与v在同一重链上的v的儿子节点(称为重儿子),w[v]表示v与其父亲节点的连边(称为v的父边)在线段树中的位置。只要把这些东西求出来,就能用logn的时间完成原问题中的操作。**重儿子:**siz[u]为v的子节点中siz值最大的,那么u就是v的重儿子。
轻儿子:v的其它子节点。 重边:点v与其重儿子的连边。 轻边:点v与其轻儿子的连边。 重链:由重边连成的路径。 轻链:轻边。剖分后的树有如下性质:
性质1:如果(v,u)为轻边,则siz[u] * 2 < siz[v]; 性质2:从根到某一点的路径上轻链、重链的个数都不大于logn。预处理:(dfs_1 && dfs_2)
dfs_1: 求出树上每个节点的深度deep[i],根的子树大小size[i],祖先的信息fa[i].以及son[i]之类的信息. dfs_2: 以根为起点,向下拓展建重链,选择最大的子树继承,其余节点以该节点为起点重新做重链; 给每个点编号,每条重链相当于区间,用数据结构维护这区间,重链首尾相接,维护即可. ⒈对于v,当son[v]存在(即v不是叶子节点)时,显然有top[son[v]] = top[v]。线段树中,v的重边应当在v的父边的后面,记w[son[v]] = totw+1,totw表示最后加入的一条边在线段树中的位置。此时,为了使一条重链各边在线段树中连续分布,应当进行dfs_2(son[v]); ⒉对于v的各个轻儿子u,显然有top[u] = u,并且w[u] = totw+1,进行dfs_2过程。 这就求出了top和w。 将树中各边的权值在线段树中更新,建链和建线段树的过程就完成了。具体代码:
void dfs_1(int now,int f,int d){ deep[now]=d;fa[now]=f;num[now]=1; for (int i=end[now]; i; i=edge[i].next) { int go=edge[i].go;if (go==f) continue; dfs_1(go,now,d+1);num[now]+=num[go]; if (!son[now] || num[go]>num[son[now]]) son[now]=go; }}
void dfs_2(int now,int number){ top[now]=number;tree[now]=++tot; pre[tree[now]]=now; if (!son[now]) return; dfs_2(son[now],number); for (int i=end[now]; i; i=edge[i].next) { int go=edge[i].go; if (go!=son[now] && go!=fa[now]) dfs_2(go,go); }}
修改:
1.点修改 :根据编号在数据结构中修改 2.区间修改:(1)若L,R在一条重链上,直接修改loc[L]-loc[R] (2)若L,R不在一条重链,一边进行修改,一边同时向上靠,靠的同一条重链上,变为情况(1) 查询操作同上…链剖线段树模板题:(不稳定传送门)