树链剖分

来源:互联网 发布:ubuntu 安装桌面 编辑:程序博客网 时间:2024/05/21 09:27

现实

现在问你一个问题,假如给你一棵树,求x到y的路径的所有点的权值和。那么,我们可以想象不同的人解这道题的思路是不一样的。
刚入门的选手:Brute Force!暴力水分~
掌握了LCA的选手:先求出x,y的LCA(最近公共祖先),然后再递归循环求答案。
现在我学会了一个新的办法:树链剖分
思想:分治思想(将一个路径分成若干个重路径)

定义

我们定义siz[x]表示x的“子孙”(包括x节点)的个数,fa[x]表示x的父亲节点。
重儿子,重边:有一点y,y为x的儿子,且siz[y]是siz[u](u ∈son[x])中最大的一个,则y为x的重儿子,(x,y)为重边。(如果siz[u](u ∈son[x])=max(siz[u](u ∈son[x]))这样的点u有多个,则随便一个儿子为重儿子),其他的儿子均为轻儿子。

算法步骤

步骤一:先用一个dfs求出siz[所有点]
步骤二:再用一个dfs找出每一个非叶子节点的重边。并将这些重边变成重链,再将这些重链压进A数组里。顺便建立一个order数组,order[x]表示点x在a数组里的位置。
步骤三:用数据结构维护树上的信息。(我用的是线段树结构,因为线段树结构对于我来说很好实现,用什么数据结构决定于你)

具体实现过程

步骤一:(伪代码)

Procedure dfs1(x,fa)    fa[x]=fa;    siz[x]=1;    此时向点x所有儿子递归    siz[x]+=siz[u](u∈son[x])

步骤二:设hv[x]表示x的重儿子,top[x]表示x所在的重链的最顶端节点
(伪代码)

Procedure dfs2(x,kk)    top[x]=kk;    order[x]=++a[0];    a[a[0]]=x;    for (u∈son[x])         if siz[u]>siz[hv[x]] hv[x]=u;    if 有重儿子 dfs2(hv[x],kk);    for (u∈son[x])          if u<>hv[x] dfs2(u,u);

最关键的是步骤3.
步骤二完成后,这棵树应该是这样子的:
这里写图片描述
我们建立一个线段树,如果我们要修改单点的值,那我们直接修改就好了,这很简单。
假设我们要求区间和,那么我们应该这样:
假设我们要求点6至点7的点权和,那么
1.先找出top[6]=1,top[7]=4.此时deep[4]>deep[1],于是将4-7上的所有点权记入答案。
2.找到fa[4]=1,此时top[6]=fa[4]=1.所以取1-6上所有点权。
下附查找的标。

procedure cz(x,y:longint);var x1,y1:longint;begin    while true do    begin        x1:=top[x];        y1:=top[y];        if (deep[x1]<deep[y1]) then        begin            swap(x,y);            swap(x1,y1);        end;        if x1=y1 then break;        find(1,n,order[x1],order[x],1);        x:=fa[x1];    end;    if order[x]>order[y] then swap(x,y);    find(1,n,order[x],order[y],1);end;

如果是求区间最大值也是一样的步骤。

总结一下

这个算法可以求一棵树的区间值。将一些点连在一起(这些店在一条链上),用线段树大大缩小了求LCA的时间。
——2016.5.13

3 0