学习笔记 树链剖分

 

 树链剖分笔记  

 

  咕咕咕了两个月的blog复活了

 

  (优秀的blog怎么可以没有表情包)

 

  

 

  (这个猫猫超级可爱的!!!)

  

  身为一个蒟蒻,就要有自知之明,NOIP写暴力,所以Lbmttw_lx就要学习各种各样比较神奇的数据结构和算法了,昨天在coldhac和Supreme_Ariy的帮助下终于写完了树链剖分,来写写这个笔记巩固一下新知吧!

 

 First Part

 

  首先我们需要知道为什么要进行树链剖分,以及树链剖分的适用范围。树链剖分一般应用于点权树,对于一棵树上的节点的点权进行一系列操作。我们先想这个问题,如果是区间的区间修改查询,我们显然可以用树状数组或者线段树维护,时间复杂度可以优化到log级别的。现在还是区间修改,单点修改,或者是区间查询,不过问题不在一个平整的区间上,而是变更到了一棵树上,我们考虑,树和一段连续的序列的区别,当树的根的出度为0且树退化为一条链的时候,就是一个序列了,我们就可以用线段树求解了,但是问题是大部分的时候树都不是链,这个时候,我们可以用树链剖分将树剖分成若干条链,在线段树上的体现就是若干段连续的区间,对于这个节点或者路径的操作,我们就可以转移到线段树上进行操作了。

 

 Second Part

 

  树链剖分的时间复杂度为nlogn^2。1e5的数据可以过,但是常数巨大,会被1e6的数据制裁掉(就像是线段树一样,一样过不了1e6的数据)

  介绍树链剖分中的几个名词,size[u]为记录以节点u根的子树大小,重儿子:在节点u中所有儿子中size值最大的那个儿子。重边:连接重儿子与根节点之间的边,轻边:根节点到其他儿子之间的边

  几个比较常用的性质:1.如果(u,v)为轻边,则一定满足,size[v]<=size[u]/2。证明过程:感性理解size的定义,重儿子的定义,因为重儿子根节点所有儿子中size值最大的那个,所以轻儿子的size一定小于或者等于它父亲的size/2

            2.从根节点u出发到任意一个节点V的路径上,经过的轻边一定不多于logn个。证明:由于性质一可知,size[v]<=size[u]/2,每次经过一条轻边,它的size值至少/2,所以至多经过nlogn条轻边就可以到达v

            3.我们称一条路径为重路径当且仅当这条重路径上的所有边都是重边,特殊的,一个点也算一条重路径。那么对于任意一个点,到根节点的路径中,至多有logn条重路径和logn条轻边,证明:因为每条重路径的起点和终点都是由轻边构成(反证法,如果不是轻边的话,这条重路径就可以继续延伸,就说明这并不是终点或者起点,故此命题不成立。得证:每条重路径的起点和重点都是由轻边构成),由于性质2可得,至多有logn条轻边,所以至多有logn条重路径

            4.一个点在且只在一条重路径上,而每条重路径也只能由根节点方向向下延展,因为每一个非叶子节点有且只有一个重儿子。

 

 Third Part

   

  我们需要对一棵树进行轻重链的剖分,剖分之后,我们考虑做出以下修改1.对于节点x-节点y的最短路径加上z  2.求出节点x-节点y的最短路径上点权之和 3.对于节点x-根节点的路径上所有点的权值都加上z 4.求出节点x-根节点的路径上所有点权之和。我们考虑所要处理的路径(u,v)我们可以分别处理这两个点到其最近公共祖先的路径,根据性质3可知,最多分解成logn条轻边和logn条重路径,那么我们现在只需要考虑如何维护这两个对象。对于重路径,我们此时可以将其看做是一个序列,只需要线段树进行维护,而对于轻边呢,我们可以直接跳过,访问下一条重路径,因为轻边的两个端点一定在某两条重路径。这两种操作的时间复杂度为logn^2和logn。所以总的时间复杂度为logn^2

  考虑如何进行轻重链的剖分,可以通过两次dfs来实现。以下为参考

inline void dfs1(int u,int fa)
{
    f[u]=fa,deep[u]=deep[fa]+1;sizze[u]=1; int maxx=0;
    for(int i=head[u];i;i=g[i].nxt){
        int to=g[i].to;
        if(to!=fa){
            dfs1(to,u); sizze[u]+=sizze[to];
            if(sizze[to]>maxx) maxx=sizze[to],son[u]=to;
        }
    }
}
dfs1

  dfs1主要计算的是size,son,deep,fa具体的意义应该都知道,son[u]为u的重儿子,以u-son[u]为重边

 1 inline void dfs2(int u,int topf)
 2 {
 3     static int cnt=0;
 4     dfn[u]=++cnt;a[cnt]=aa[u];top[u]=topf;
 5     if(son[u]) dfs2(son[u],topf);
 6     for(int i=head[u];i;i=g[i].nxt){
 7         int to=g[i].to;
 8         if(to!=f[u]&&to!=son[u]) dfs2(to,to);
 9     }
10 }
dfs2

  dfs2主要计算的是top,dfn以及aa,其意义如下,top为x所在的重路径的顶部顶点,dfn为dfs序,主要维护的是链能在线段树上有连续的下标,这样就可以用线段树维护了。aa的意义是节点u在线段树中的权值。

 

 例题https://www.luogu.org/problem/P3384树链剖分的模板

  

  我们没有说到的,只有对于链的操作了,(操作3和4可以直接基于线段树实现),考虑如何维护1操作和2操作,我们可以类似于求LCA那样,不断的往上跳,然后对于目前的该节点的dfn值,不断的求和,直到跳到同一个deep上,即跳到了同一个top,求和这样,权值相加同理。

  那么我们就附上这道题操作2和操作1的代码吧23333

  

inline void add_link(int x,int y,int z)
{
    while(top[x]!=top[y]){
        if(deep[top[x]]<deep[top[y]]) swap(x,y);
        modify(1,dfn[top[x]],dfn[x],z);x=f[top[x]];
    }
    if(deep[x]>deep[y]) swap(x,y);modify(1,dfn[x],dfn[y],z);
}
inline int sum_link(int x,int y)
{
    ll ans=0;
    while(top[x]!=top[y]){
        if(deep[top[x]]<deep[top[y]]) swap(x,y);
        ans+=getsum(1,dfn[top[x]],dfn[x]);x=f[top[x]];
    }

    if(deep[x]>deep[y]) swap(x,y); ans+=getsum(1,dfn[x],dfn[y]);
    return ans%mo;
}

 

  Thanks!

 

 

  

posted @ 2019-10-10 19:25  落筱  阅读(284)  评论(1编辑  收藏  举报