树链剖分

树链剖分一般用来求树上区间查询、修改 或 求LCA

学习树链剖分之前要学会线段树

树链剖分线段树维护

首先弄清几个名字

重儿子 所有儿子中,子树大小最大那个儿子
轻儿子 除重儿子以外的儿子
重边 连接重儿子的边
轻边 连接轻儿子的边
重链 由重边组成的路径
轻链 由轻边组成的路径

举个例子:

剖分过程要计算的值

father[i] i在树中的父亲
d[i] i在树中的深度
size[i] i的子树大小
son[i] i的重儿子
top[i] i在的重路径的顶部
seq[i] i结点在线段树中的位置
rev[i] 线段树的第i个位置在树中的位置

预处理

两遍dfs

第一遍dfs求出前四个值

void dfs1(int u,int fa)
{
    father[u]=fa;
    d[u]=d[fa]+1;
    size[u]=1;
    for (int i=h[u];i!=0;i=e[i].next)
    {
        int v=e[i].to;
        if (v==fa) continue;
        dfs1(v,u);
        size[u]+=size[v];//累加字数个数
        if (size[v]>size[son[u]]) son[u]=v;//存下重儿子
    }
}

第二遍dfs求出后三个值

void dfs2(int u,int fa)
{
    if (son[u])
    {
        seg[son[u]]=++cnt;//存下位置
        top[son[u]]=top[u];
        rev[cnt]=son[u];
        dfs2(son[u],u);
    }
    for (int i=h[u];i!=0;i=e[i].next)
    {
        int v=e[i].to;
        if (!top[v])
        {
            seg[v]=++cnt;
            rev[cnt]=v;
            top[v]=v;
            dfs2(v,u);
        }
    }
}

查询

查询(u,v)的LCA,选则两点深度更深的往上跳,直到 u==v;

假设u,v不在同一条重链上,那么可以直接跳到father[top[u/v]];

调过的是一段区间,所以还要记录区间[seg[u/v],seg[top[u/v]]];

当u,v的top相同时,表示他们在同一重链上,那么u,v深度最小的就是原路径的LCA

void ask(int l,int r)
{
    int fl=top[l],fr=top[r];
    while (fl!=fr)
    {
        if (d[fl]<d[fr]) swap(l,r),swap(fl,fr);
        query(1,1,cnt,seg[fl],seg[l]);
        l=father[fl],fl=top[l];
    }
    if (d[l]>d[r]) swap(l,r);
    query(1,1,cnt,seg[l],seg[r]);
}

例题

树的统计

原文地址:https://www.cnblogs.com/nibabadeboke/p/11329804.html