首页 > 学院 > 开发设计 > 正文

自己对树链剖分的理解(模板)

2019-11-14 10:14:47
字体:
来源:转载
供稿:网友

        说到树链剖分,其实故事还挺多的。我记得在高中的OI经历中,我曾无数次听到这个名词,各种省赛、邀请赛貌似都会考这个东西,那时我觉得树链剖分深不可测,是我等蒟蒻不能理解的东西……然后我还记得,某年(好像是NOip2014?)有一题貌似也要用树链剖分。总之是被虐的一B,然后现在觉得这个东西也没什么难的……

        好吧,言归正传。所谓树链剖分其实就是树的轻重链剖分,而树链就是指从某一父节点一直到叶子节点的一条链。下面先介绍几个概念:

                1、重儿子:父节点的所有儿子中,子树节点数目最多的儿子

                2、重边:连接父节点和儿子的边

                3、重链:从父节点往下所有重儿子连在一起的链

        有了这些定义,我们就能显而易见的发现:任何节点都只属于一条树链。然后按照这样把一棵树剖分成这样一条一条的树链,树链剖分就完成了,如图:

                                                        

其中这棵树被剖分成了红、绿、蓝三条链,但是要注意,圈出来的两条边其实是不属于树链的。是的完成了这个,树链剖分就已经做完了。其实剖分很容易理解,但是问题是这样做了有什么用呢?这个坑我们后面填~接下来先说说如何实现它。

        第一步当然是对于每个非叶子节点都要选取一条重边以及重儿子,这就是重链的延伸方向。如你所想,用dfs很容易实现,依次遍历该节点的子树,然后挑选可达深度最大的点作为重儿子,具体代码如下:

inline void dfs1(int u, int f, int d)			//u为当前节点,f为父亲,d为当前深度{    dep[u] = d;						//先记下深度    size[u] = 1;					//size[]表示子树的节点数目    son[u] = 0;						//son[]表示重儿子    fa[u] = f;    efor(i,u)						//遍历子树	{        int ff = g[i].y;        if (ff == f) continue;        dfs1(ff, u, d + 1);        size[u] += size[ff];        if (size[son[u]] < size[ff]) son[u] = ff;	//选取节点数最多的点作为重儿子    }}

        标记完重儿子后,我们要把每条链连起来,真正形成链。那么,我们用什么表示一条链呢?我们知道,树状数组、线段树、平衡树都是能支持在一段区间里维护性质,若我们能把一条链上的节点弄在一段连续的区间中,那我们就容易处理这些问题了。于是便想到了对每个节点进行重(chong)标号,一条链上的点的新编号一定是连续的,具体的还是利用dfs来实现,代码:

inline void dfs2(int u, int tp)				//tp表示当前树链的链头,其实这个操作应该叫label{    top[u] = tp;					//top[]表示节点i所属树链的链头,方便查询和修改    id[u] = ++num;					//id[]表示一个节点新的id(编号)    Rank[id[u]]=u;					//Rank[]表示新编号i对应原来的编号,即Rank[id[i]]=i    if (son[u]) dfs2(son[u], tp);			//先对同一条链上的重儿子标号    efor(i,u)	{        int ff = g[i].y;        if (ff == fa[u] || ff == son[u]) continue;        dfs2(ff, ff);					//对其他儿子标号    }}

        这样通过dfs1和dfs2,我们就完成了树链剖分,现在开始填坑……

        树链剖分适合解决树上的一些修改询问以及求LCA之类的问题,同过把树进行剖分,我们可以用很多数据结构维护每一条链,然后再通过求LCA,我们可以解决对于树上任意两点的询问问题。例如不断的对边权修改,求任意两点之间的最大边或最大值,这就可以用线段树对每个树链进行维护。另外,还值得一提的是树链剖分后求LCA的方法,由于我们在dfs2的时候记录了top[]数组,所以用朴素法就能很快的求出LCA,代码:

inline int LCA(int u, int v){    int tp1 = top[u], tp2 = top[v];			//tp为链头    while (tp1 != tp2)    {        if (dep[tp1] < dep[tp2])			//判断深度	{            swap(tp1, tp2);            swap(u, v);        }        u = fa[tp1];					//每次向上走了tp1所以速度很快        tp1 = top[u];    }    return tp1;}

        令我不满意的是,我好像还是没有说清楚具体如何进行修改和查询,好吧,下次结合具体的题目再说把,再给自己挖一个坑……

        最后说说我最不擅长的复杂度吧,由于是按照轻重链剖分的,所以有这样的定理:

                1、对于轻边(u,v),size[v]<=size[u]/2

                2、从根到某一点的路径上,不超过logN条轻边

                3、重链总数不超过logN

        因此剖分的时间复杂度为O(N);用数据结构去维护每一条链时,每次的时间复杂度为O(logN),当然你若不是用高级数据结构我也没办法咯=_=……,所以总的来说时间复杂度是O(NlogN)级别的。

        最后,我又看了一下百度,我觉的百科说的还是更有道理,树链剖分是用来维护树的路径信息的一种算法?数据结构?我还是不来定义了……

        对了,那个坑我会填的^_^~~~


发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表