首页 > 其他分享 >树链剖分

树链剖分

时间:2023-01-30 16:56:14浏览次数:47  
标签:sz 剖分 int top 树链 dfn 节点

简述

树链剖分用于将树分割成若干条链的形式,以维护树上路径的信息。
具体来说,将整棵树剖分为若干条链,使它组合成线性结构,然后用其他的数据结构维护信息。
树链剖分(树剖/链剖)有多种形式,如 重链剖分,长链剖分 和用于 Link/cut Tree 的剖分(有时被称作“实链剖分”),大多数情况下(没有特别说明时),“树链剖分”都指“重链剖分”。

重链剖分

重链剖分可以将树上的任意一条路径划分成不超过 \(O(\log n)\) 条连续的链,每条链上的点深度互不相同(即是自底向上的一条链,链上所有点的 LCA 为链的一个端点)。
重链剖分还能保证划分出的每条链上的节点 DFS 序连续,因此可以方便地用一些维护序列的数据结构(如线段树)来维护树上路径的信息。
如:

  1. 修改 树上两点之间的路径上 所有点的值。
  2. 查询 树上两点之间的路径上 节点权值的 和/极值/其它(在序列上可以用数据结构维护,便于合并的信息)。

定义

  • 重儿子:对于每一个非叶子节点,它的儿子中 儿子数量最多的那一个儿子 为该节点的重儿子
  • 轻儿子:对于每一个非叶子节点,它的儿子中 非重儿子 的剩下所有儿子即为轻儿子
  • 叶子节点没有重儿子也没有轻儿子(因为它没有儿子。。)
  • 重边:连接任意两个重儿子的边叫做重边
  • 轻边:剩下的即为轻边
  • 重链:相邻重边连起来的 连接一条重儿子 的链叫重链
  • 对于叶子节点,若其为轻儿子,则有一条以自己为起点的长度为1的链
  • 每一条重链以轻儿子为起点

code

namespace qwq{ //树剖部分
    //第一个dfs(init)记录父节点(fa)、深度(dep)、子树大小(sz)、重子节点(son)
    int dep[N],fa[N],son[N],sz[N];	
    il void init(int u,int pre){ 
        int mxson=-1;
        dep[u]=dep[pre]+1,fa[u]=pre,sz[u]=1;
        for(ri int i=edge::h[u];i;i=edge::e[i].nxt){
            int v=edge::e[i].v;
            if(v==pre) continue;
            init(v,u),sz[u]+=sz[v];
            if(sz[v]>mxson) mxson=sz[v],son[u]=v;
        }
        return;		
    }
    //第二个dfs(getnum)记录所在链的链顶(top)、重边优先遍历的dfs序(dfn)
    int top[N],dfn[N],cnt,wi[N]; //点权(wi)可不用……
    il void getnum(int u,int tp){
        top[u]=tp,dfn[u]=++cnt,wi[cnt]=w[u];
        if(!son[u]) return;
        getnum(son[u],tp); //先处理重儿子
        for(ri int i=edge::h[u];i;i=edge::e[i].nxt){
            int v=edge::e[i].v;
            if(v==fa[u]||v==son[u]) continue;
                getnum(v,v); //再处理轻儿子
            }
        return;
    }
} using namespace qwq;

应用

最近公共祖先

不断向上跳重链,当跳到同一条重链上时,深度较小的结点即为 LCA。
向上跳重链时需要先跳所在重链顶端深度较大的那个。

il int lca(int u,int v){
    while(top[u]!=top[v]){
        if(dep[top[u]]>dep[top[v]]) u=fa[top[u]];
        else v=fa[top[v]]; 
    }
    return dep[u]>dep[v]?v:u;
}

维护路径

链上的 DFS 序是连续的,可以使用线段树、树状数组维护。
每次选择深度较大的链往上跳,直到两点在同一条链上。
同样的跳链结构适用于维护、统计路径上的其他信息。

namespace operate{
    il void op1(int x,int y,int z){ //将树从x到y结点最短路径上所有节点的值都加上z。
        while(top[x]!=top[y]){
            if(dep[top[x]]<dep[top[y]]) swap(x,y);
            add(1,1,n,dfn[top[x]],dfn[x],z),x=fa[top[x]];
        }
        if(dep[x]>dep[y]) swap(x,y);
        return add(1,1,n,dfn[x],dfn[y],z);
    }
    il int op2(int x,int y){ //求树从x到y结点最短路径上所有节点的值之和
        int as=0;
        while(top[x]!=top[y]){
            if(dep[top[x]]<dep[top[y]]) swap(x,y);
            as=(as+ask(1,1,n,dfn[top[x]],dfn[x]))%mod;
            x=fa[top[x]];
        }
        if(dep[x]>dep[y]) swap(x,y);
        return as=(as+ask(1,1,n,dfn[x],dfn[y]))%mod;
    }
}

维护子树

子树中的结点的 DFS 序是连续的。
节点\(x\)子树所在连续区间为\([dfn[x],dfn[x]+sz[x]-1]\)。
这样就把子树信息转化为连续的一段区间信息。

namespace operate{
    il void op3(int x,int z){ //将以x为根节点的子树内所有节点值都加上z
        return add(1,1,n,dfn[x],dfn[x]+sz[x]-1,z); 
    }
    il int op4(int x){ //求以x为根节点的子树内所有节点值之和
        return ask(1,1,n,dfn[x],dfn[x]+sz[x]-1);		
    }
}
AC·code
#include<bits/stdc++.h>
#define il inline
#define cs const
#define ri register
using namespace std;
cs int N=1e5+5;
int n,m,root,mod,w[N];

namespace edge{
    int h[N];
    struct node{int v,nxt;}e[N*2];
    il void add(int u,int v,int id){
        e[id*2-1]={v,h[u]},h[u]=id*2-1;
        return e[id*2]={u,h[v]},h[v]=id*2,void();
    }
}

namespace qwq{ //树剖部分
    int dep[N],fa[N],son[N],sz[N];	
    il void init(int u,int pre){
        int mxson=-1;
        dep[u]=dep[pre]+1,fa[u]=pre,sz[u]=1;
        for(ri int i=edge::h[u];i;i=edge::e[i].nxt){
            int v=edge::e[i].v;
            if(v==pre) continue;
            init(v,u),sz[u]+=sz[v];
            if(sz[v]>mxson) mxson=sz[v],son[u]=v;
        }
        return;		
    }
    int top[N],dfn[N],cnt,wi[N];
    il void getnum(int u,int tp){
        top[u]=tp,dfn[u]=++cnt,wi[cnt]=w[u];
        if(!son[u]) return;
        getnum(son[u],tp);
        for(ri int i=edge::h[u];i;i=edge::e[i].nxt){
            int v=edge::e[i].v;
            if(v==fa[u]||v==son[u]) continue;
                getnum(v,v);
            }
        return;
    }
} using namespace qwq;

namespace tree{	//线段树
    #define ls (rt<<1)
    #define rs ((rt<<1)|1)
    #define mid ((l+r)>>1)

    int tr[N<<2],lz[N<<2];
    il void pushup(int rt){
        return tr[rt]=(tr[ls]+tr[rs])%mod,void();
    }
    il void pushdown(int rt,int l,int r){
        if(!lz[rt]) return;
        lz[ls]=(lz[ls]+lz[rt])%mod;
        lz[rs]=(lz[rs]+lz[rt])%mod;
        tr[ls]=(tr[ls]+(lz[rt]*(mid-l+1))%mod)%mod;
        tr[rs]=(tr[rs]+(lz[rt]*(r-mid))%mod)%mod;
        return lz[rt]=0,void();
    }
    il void build(int rt,int l,int r){
        if(l==r) return tr[rt]=wi[l],void();
        build(ls,l,mid),build(rs,mid+1,r);
        return pushup(rt);
    }
    il void add(int rt,int l,int r,int ql,int qr,int k){
        if(ql<=l&&r<=qr){
            tr[rt]=(tr[rt]+(r-l+1)*k%mod)%mod;
            return lz[rt]=(lz[rt]+k)%mod,void();
        } pushdown(rt,l,r);
        if(mid>=ql) add(ls,l,mid,ql,qr,k); 
        if(mid<qr) add(rs,mid+1,r,ql,qr,k);
        return pushup(rt);
    }
    il int ask(int rt,int l,int r,int ql,int qr){
        if(ql<=l&&r<=qr) return tr[rt];
        pushdown(rt,l,r);int as=0;
        if(mid>=ql) as=(as+ask(ls,l,mid,ql,qr))%mod;
        if(mid<qr) as=(as+ask(rs,mid+1,r,ql,qr))%mod;
        return as;
    }

    #undef ls
    #undef rs
    #undef mid 
} using namespace tree;

namespace operate{
    il void op1(int x,int y,int z){ //将树从x到y结点最短路径上所有节点的值都加上z。
        while(top[x]!=top[y]){
            if(dep[top[x]]<dep[top[y]]) swap(x,y);
            add(1,1,n,dfn[top[x]],dfn[x],z),x=fa[top[x]];
        }
        if(dep[x]>dep[y]) swap(x,y);
        return add(1,1,n,dfn[x],dfn[y],z);
    }
    il int op2(int x,int y){ //求树从x到y结点最短路径上所有节点的值之和
        int as=0;
        while(top[x]!=top[y]){
            if(dep[top[x]]<dep[top[y]]) swap(x,y);
            as=(as+ask(1,1,n,dfn[top[x]],dfn[x]))%mod;
            x=fa[top[x]];
        }
        if(dep[x]>dep[y]) swap(x,y);
        return as=(as+ask(1,1,n,dfn[x],dfn[y]))%mod;
    }
    il void op3(int x,int z){ //将以x为根节点的子树内所有节点值都加上z
        return add(1,1,n,dfn[x],dfn[x]+sz[x]-1,z);
    }
    il int op4(int x){ //求以x为根节点的子树内所有节点值之和
        return ask(1,1,n,dfn[x],dfn[x]+sz[x]-1);		
    }
} using namespace operate;

int main(){
    cin>>n>>m>>root>>mod;
    for(ri int i=1;i<=n;++i) cin>>w[i],w[i]%=mod;
    for(ri int i=1,u,v;i<n;++i) cin>>u>>v,edge::add(u,v,i);
    init(root,root),getnum(root,root),build(1,1,n);
    for(ri int i=1,op,x,y,z;i<=m;++i){
        cin>>op>>x;
        if(op==1) cin>>y>>z,z%=mod,op1(x,y,z);
        if(op==2) cin>>y,cout<<op2(x,y)<<'\n';
        if(op==3) cin>>z,z%=mod,op3(x,z);
        if(op==4) cout<<op4(x)<<'\n';
    }
    return 0;
} 

长链剖分

  • 长链剖分本质上就是另外一种链剖分方式。
  • 长链剖分的剖分方法与轻重链剖分极其相似,只需要把以子树大小判断重儿子改成以节点深度判断即可
  • 长链剖分从一个节点到根的路径的轻边切换条数是 \(\sqrt{n}\) 级别的
  • 一般情况下可以使用长链剖分来优化的 DP 会有一维状态为深度维。
    to be continue~

标签:sz,剖分,int,top,树链,dfn,节点
From: https://www.cnblogs.com/windymoon/p/17076579.html

相关文章

  • 【YBT2023寒假Day1 B】不跪模样(树链剖分)(线段树)
    不跪模样题目链接:YBT2023寒假Day1B题目大意给你一棵有根数,点有点权,两种操作:对于所有x子树内与x距离不超过2的点,将其点权加v。询问x子树中,满足i<=j且i,j......
  • 轻重链剖分板子
    //LuoguP3384【模板】轻重链剖分/树链剖分#include<iostream>#include<cstring>#include<algorithm>#include<vector>#definelcu<<1#definercu<<1|1usin......
  • 2568. 树链剖分
    2568.树链剖分原题链接思路:先将一颗树进行树链剖分,再将它的dfs序求出来利用dfs序将线段树模拟出来(build出来)再将输入的路径进行切割导入线段树处理,直到两个元素......
  • 算法学习笔记(6): 树链剖分
    树链剖分树链剖分是一个很神奇,但是在树上可以完成一些区间操作问题简单来说,就是把一棵树分成一条条的链,通过维护链上的信息来维护整棵树的信息基础知识可以参考我的另外......
  • 树链剖分 学习笔记
    树链剖分是一种思想,可以用来通过给树中每个点重新编号,从而让树中任意一条路径转化成\(O(logn)\)段连续区间(链)。树中任意一条路径均可以变成不超过\(logn\)段连续区间......
  • P2146 [NOI2015] 软件包管理器 树链剖分
    //题目大意:给定一棵树,树上的每个节点是一个软件,现在给出如下两个指令,install与uninstall,//如果需要installx号软件,那么我需要安装他到根节点之间的所有软件;如......
  • ZJOI2008, 树的统计 树链剖分模板
    //题意:给定一棵树,现在我需要询问以下操作//1.q,u之间的最小值//2.q,u之间的简单路径的权值和//3.修改树上q点的权值//思路:如果是在一段序列上的问......
  • 处理手法学习(树链剖分)
    今天vp了两场cf,感觉对开眼界还是很有用的,手感也回来了点首先给出一些点,如何找出是否属于同一条链首先暴力方法就是每次dfs,在分叉大于2的地方看看是否包含所有的点这是个......
  • BZOJ 3252 攻略(长链剖分模板 链的常用性质 贪心)
    BZOJ3252攻略​ 给定一棵带边权的树,选择k个叶子结点,使这些叶子结点与根节点相连,形成k条路径。输出被路径覆盖的所有边的边权和的最大值(同一条边若被重复覆盖只算一......
  • 最大面积最小的三角剖分 Minimax Triangulation
    #include<iostream>#include<algorithm>#include<cmath>#include<vector>usingnamespacestd;#definemistake(P)(fabs(P)<eps?0:(P<0?-1:1))......