*点击

树链剖分

树链剖分

简介

树链剖分用于将树分割成若干条链的形式,以维护树上路径的信息。

具体来说,将整棵树剖分为若干条链,使它组合成线性结构,然后用其他的数据结构维护信息;

 

重边与轻边,重链与轻链

在将一棵树分成若干条链时,我们最好不要盲目的分;

有一种有效的分割方法就是,分成重链与轻链;

重链:连续的重边组成一条链;

轻链:连续的轻边组成一条链;

那么怎么判断重边和轻边呢?

我们定义

重子节点 :表示其子节点中子树最大的子结点。如果有多个子树最大的子结点,取其一。如果没有子节点,就无重子节点;

轻子节点 :表示剩余的所有子结点;

那么重边和轻边就是

重边:重子节点与它父节点相连的一条边;

轻边:轻子节点与它父节点相连的一条边;

如图

 

代码实现

首先我们给出一些;

重要的数组

$f[x]$        表示 $x$ 节点的父节点;

$son[x]$   表示 $x$ 节点的重子节点;

$dep[x]$   表示 $x$ 节点的深度;

$size[x]$   表示 $x$ 节点的子树的节点个数;

$top[x]$    表示 $x$ 节点所在这条链上的顶部节点;

$id[x]$      表示 $x$ 节点的$DFN$序,也就是在线段树中的编号;

$aa[x]$     表示 $x$ $DFN$序的节点权值;

 

那么

剖分代码

inline void dfs(ll x,ll fa)//找重子节点 
{
    size[x]=1;
    f[x]=fa;//记录父节点 
    for(re ll i=head[x];i;i=e[i].stb)
    {
        ll xx=e[i].to;
        if(xx==fa)//不能遍历到父节点 
            continue;
        dep[xx]=dep[x]+1;//统计深度 
        dfs(xx,x);
        size[x]+=size[xx];//统计子树节点数 
        if(!son[x]||size[xx]>size[son[x]])
            son[x]=xx;//找重子节点,也就是子树节点数最多的子节点 
    }
}
ll tot=0;//统计在线段树中的编号 
inline void DFS(ll x,ll t)//t 表示这条链的顶部 
{
    top[x]=t;//记录 
    id[x]=++tot;//记录在线段树中的编号 
    aa[tot]=w[x];//记录在线段树中的权值 
    if(!son[x])//如果没有重子节点 
        return;//返回 
    DFS(son[x],t);//先遍历重子节点 
    for(re ll i=head[x];i;i=e[i].stb)
    {
        ll xx=e[i].to;
        if(xx==f[x]||xx==son[x])//遍历轻子节点 
            continue;
        DFS(xx,xx);//每个开始的轻子节点的链顶就是自己 
    }
}

 

调用代码

    dfs(1,0);
    DFS(1,1);//1 所在链的顶部就是自己

 

 求LCA

inline ll LCA(ll x,ll y)
{
    while(top[x]!=top[y])//如果不在同一条重链 
    {
        if(dep[top[x]]>dep[top[y]])//将深度大往上跳,使得最后 x, y在同一条链上 
            x=f[top[x]];
        else
            y=f[top[y]];
    }
    if(dep[x]<dep[y]) // x,y 深度较小的就是 LCA 
        return x;
    else
        return y;
}//读者可以按照上面的图模拟一下 

 

 

 

线段树维护信息

不同的是求树上两个点之间的信息;

修改就直接用线段树模板;

 

建树

inline void build(ll p,ll l,ll r)//建树 
{//果然没改啥吧 
    a[p].l=l; a[p].r=r;
    if(l==r)
    {
        a[p].v=aa[l];//熟悉的赋值操作 
        return;
    }
    ll mid=(l+r)>>1;
    build(L(p),l,mid);
    build(R(p),mid+1,r);
    doit(p);//熟悉的传递 
}
View Code

 

调用

build(1,1,n);

 

单点修改

inline void change(ll p,ll x,ll y)//单点修改 
{
    if(a[p].l==a[p].r)
    {
        a[p].v=y;
        return;
    }
    ll mid=(a[p].l+a[p].r)>>1;
    if(x<=mid)
        change(L(p),x,y);
    else
        change(R(p),x,y);
    doit(p);
}
View Code

调用

change(1,id[x],y);//改下编号就好了

 

树上两点间求和

收先需要写一个线段树区间求和来维护;

inline ll findsum(ll p,ll l,ll r)//找区间sum 
{
    if(l<=a[p].l&&a[p].r<=r)
        return a[p].v;
    ll sum=0;
    ll mid=(a[p].l+a[p].r)>>1;
    if(l<=mid)
        sum+=findsum(L(p),l,r);
    if(r>mid)
        sum+=findsum(R(p),l,r);
    return sum;
}
View Code

然后就让两个点不停加上这个点与链顶的距离,在往上跳到上面一条链;

最后使得两个点在同一条链上时,加上两个点之间的区间和即可;

inline ll qsum(ll x,ll y)
{
    ll sum=0;
    while(top[x]!=top[y])//我们需要是 x 节点跳到与 xx 节点在同一条链上 
    {
        if(dep[top[x]]<dep[top[y]])//深度大的往上跳 
            swap(x,y);
        sum+=findsum(1,id[top[x]],id[x]);//统计 x 到链顶的 sum 
        x=f[top[x]];// 跳到下一个区间,也就是在 top[x] 上面的链 
    }
    if(dep[x]<dep[y])
        swap(x,y);
    sum+=findsum(1,id[y],id[x]);//在统计下 x 到 xx 的区间sum 
    //此时 x 与 xx 是在同一条链上 
    return sum;
}

 

树上两点间求最大边权

和两点间求和差不多思路,那么还是放下代码吧;

inline ll findmax(ll p,ll l,ll r)//区间最大值 
{
    if(l<=a[p].l&&a[p].r<=r)
        return a[p].mx;
    ll sum=-(1<<30);
    ll mid=(a[p].l+a[p].r)>>1;
    if(l<=mid)
        sum=max(sum,findmax(L(p),l,r));
    if(r>mid)
        sum=max(sum,findmax(R(p),l,r));
    return sum;
}
View Code
inline ll qmax(ll x,ll y)
{
    ll sum=-(1<<30);
    while(top[x]!=top[y])//我们需要是 x 节点跳到与 xx 节点在同一条链上 
    {
        if(dep[top[x]]<dep[top[y]])//深度大的往上跳 
            swap(x,y);
        sum=max(sum,findmax(1,id[top[x]],id[x]));//统计 x 到链顶的 最大值 
        x=f[top[x]];//  跳到下一个区间,也就是在 top[x] 上面的链 
    }
    if(dep[x]<dep[y])
        swap(x,y);
    sum=max(sum,findmax(1,id[y],id[x]));//在统计下 x 到 xx 的区间最大值
    //此时 x 与 xx 是在同一条链上 
    return sum;
}

 

读者可以结合上面图的进行理解;

posted @ 2020-08-17 21:59  木偶人-怪咖  阅读(190)  评论(0编辑  收藏  举报
*访客位置3D地图 *目录