参考博客:https://blog.csdn.net/qq_18661257/article/details/52766738
树链剖分主要针对路径进行处理,我们平时处理一颗树的路径的方案一般就是求解LCA的各类算法,在线ST,离线tarjan,以及LCA在线倍增法,其实LCA在线倍增法在求解路径方面已经颇为让人满意了,O(nlogn)O(nlogn)构建,O(logn)O(logn)进行查询,比如一般的求解路径上的最大边权,点权之类的一般LCA倍增法都可以解决,如果再离线一下,或许复杂度更低。
但是树链剖分的独特之处就是可以对路径进行修改,一般结合线段树进行处理,查询更新的复杂度就是O(logn),这是LCA在线倍增所不能做到的。
树链剖分基本性质
- 如果(v,u)(v,u)为轻边,则ve[u]×2<ve[v]ve[u]×2<ve[v];
- 从根到某一点的路径上轻链、重链的个数都不大于lognlogn。
相关数组定义
deep[u]:来保存当前节点uu的深度
fa[u]: 用来保存当前节点uu的父亲
ve[u]: 用来保存以uu为根的子树节点个数
son[u]: 用来保存当前节点uu的重儿子
top[u]:用来保存当前节点uu的所在链的顶端节点
p[u]: 用来保存当前节点uu在线段树中的位置
fp[]:用来保存线段树相应位置保存的是当前哪个节点
dfs1是为了找重边,记录下所有的重边以及相关关系
void dfs1(int u,int p,int d)
{
deep[u] = d;
fa[u] = p;
ve[u] = 1;
son[u] = -1;
for(int i = head[u];i!= -1;i = s[i].ne)
{
if(s[i].v == p) continue;
dfs1(s[i].v,u,d+1);
ve[u]+= ve[s[i].v];
if(son[u] == -1||ve[s[i].v]> ve[son[u]])
son[u] = s[i].v;
}
return ;
}
dfs2就是为了连接重边形成重链,即将这条长路径给连接起来,具体步骤:以根节点为起点,沿着重边向下拓展,拉成重链,不在当前重链上的节点,都以该节点为起点向下重新拉一条重链。
void dfs2(int u,int sp)
{
top[u] = sp;
p[u] = ++sz;
fp[p[u]] = u;
if(son[u] == -1) return ;
dfs2(son[u],sp);
for(int i = head[u];i!= -1;i = s[i].ne)
{
if(s[i].v == son[u]||s[i].v == fa[u]) continue;
dfs2(s[i].v,s[i].v);
}
return ;
}
查询两点之间路径权值和为例
ll demand(int x,int y)
{
int fx = top[x];
int fy = top[y];
ll ans = 0;
while(fx!= fy)
{
if(deep[fx]< deep[fy])
{
swap(fx,fy);
swap(x,y);
}
ans+= query(1,p[fx],p[x]);
x = fa[fx];
fx = top[x];
}
if(x == y) return ans;
if(deep[x]> deep[y]) swap(x,y);
ans+= query(1,p[son[x]],p[y]);
return ans;
}
分析:
- 如果u与v在同一条重链上,那么就直接修改了,因为他们是连续的
- 如果不在一条重链上,则往一条重链上靠拢,然后就会编程一条重链