树链剖分 详解

本文介绍了树剖思想在数据结构中的应用,包括重儿子、轻儿子和重链的概念,以及如何利用线段树等数据结构对区间进行高效维护。给出了一个基于LCA的实现方法和C++代码示例,展示了如何在实际场景中使用这些概念解决路径和子树和的问题。
摘要由CSDN通过智能技术生成

1.树剖的思想

树剖本质上是维护一些树上的操作,把树中任意一段路径划分成不超过2log2n​段连续区间

然后就上一些数据结构(线段树,树状数组,平衡树......)维护区间即可

2.一些关于树剖的定义

  • 重/轻儿子:对于任意一个非叶节点的节点,都会有一个重儿子,其定义为其儿子节点中所在节点最多子树的子节点,其余为轻儿子。

  • 重/轻边:由一个节点连向它的重儿子的边叫做重边,其余的叫做轻边。

  • 重链:指由重链构成的极大路径。

(如图,红色点为重儿子,白色点为轻儿子,红色边为重边,白色边为轻边)

观察上图,我们可以发现一个轻儿子都是一个重链的顶点,且每一个重链的dfs序都是连续的。(优先遍历重儿子)

3.如何实现

上文所述:树中任意一段路径可以划分成不超过log2n​段连续区间

如何实现?

考虑类似LCA式去求解

从这条路径的两个端点向上爬,每一次跳到它所在重链的父节点上,直到跳到这两个点的最近公共重链上为止,这样就很好地完成了上面的问题。

4.代码实现

先贴一下代码,在注释里详细说吧

#include <bits/stdc++.h>
using namespace std;
#define int long long 
const int N=100010,M=N*2;
int n,m;
int w[N],h[N],e[M],ne[M],idx;//邻接表
int id[N],nw[N],cnt;//id表示dfs序,nw表示点权,cnt表示dfs序当前分配到的位置
int dep[N],sz[N],top[N],son[N],fa[N];//dep表示每个点的深度,sz表示子树大小,top表示每个点所在重链的顶点,son表示重儿子,fa表示父节点
struct node//线段树
{
    int l,r;
    int add,sum;
}tr[N*4];
void pushup(int u)
{
    tr[u].sum=tr[u<<1].sum+tr[u<<1|1].sum;
}
void pushdown(int u)
{
    node &root=tr[u],&left=tr[u<<1],&right=tr[u<<1|1];
    if(root.add)
    {
        left.add+=root.add;left.sum+=root.add*(left.r-left.l+1);
        right.add+=root.add;right.sum+=root.add*(right.r-right.l+1);
        root.add=0;
    }
}
void build(int u,int l,int r)
{
    tr[u]={l,r,0,nw[r]};
    if(l==r)return;
    int mid=l+r>>1;
    build(u<<1,l,mid);build(u<<1|1,mid+1,r);
    pushup(u);
}
void update(int u,int l,int r,int d)
{
    if(tr[u].l>=l&&tr[u].r<=r)
    {
        tr[u].sum+=(tr[u].r-tr[u].l+1)*d;
        tr[u].add+=d;
        return;
    }
    pushdown(u);
    int mid=tr[u].l+tr[u].r>>1;
    if(l<=mid)update(u<<1,l,r,d);
    if(r>mid)update(u<<1|1,l,r,d);
    pushup(u);
}
int query(int u,int l,int r)
{
    if(tr[u].l>=l&&tr[u].r<=r)return tr[u].sum;
    pushdown(u);
    int res=0;
    int mid=tr[u].l+tr[u].r>>1;
    if(l<=mid)res+=query(u<<1,l,r);
    if(r>mid)res+=query(u<<1|1,l,r);
    return res;
}
void add(int a,int b)//建一条a->b的的边
{
    e[idx]=b;ne[idx]=h[a];h[a]=idx++;
}
void dfs1(int u,int father,int depth)//第一遍dfs,维护dep,fa,sz,son的信息
{
    dep[u]=depth;fa[u]=father;sz[u]=1;
    for(int i=h[u];i!=-1;i=ne[i])
    {
        int j=e[i];
        if(j==father)continue;
        dfs1(j,u,depth+1);
        sz[u]+=sz[j];
        if(sz[son[u]]<sz[j])son[u]=j;
    }
}
void dfs2(int u,int t)//第二遍dfs,维护id,nw,top的信息
{
    id[u]=++cnt;nw[cnt]=w[u];top[u]=t;
    if(!son[u])return;
    dfs2(son[u],t);
    for(int i=h[u];i!=-1;i=ne[i])
    {
        int j=e[i];
        if(j==fa[u]||j==son[u])continue;
        dfs2(j,j);
    }
}
void update_path(int u,int v,int k)//修改路径
{
    while(top[u]!=top[v])//向上跳的过程
    {
        if(dep[top[u]]<dep[top[v]])swap(u,v);
        update(1,id[top[u]],id[u],k);
        u=fa[top[u]];
    }
    if(dep[u]<dep[v])swap(u,v);
    update(1,id[v],id[u],k);
}
void update_tree(int u,int k)//修改子树
{
    update(1,id[u],id[u]+sz[u]-1,k);
}
int query_path(int u,int v)//查询路径和
{
    int res=0;
    while(top[u]!=top[v])//同上
    {
        if(dep[top[u]]<dep[top[v]])swap(u,v);
        res+=query(1,id[top[u]],id[u]);
        u=fa[top[u]];
    }
    if(dep[u]<dep[v])swap(u,v);
    res+=query(1,id[v],id[u]);
    return res;
}
int query_tree(int u)//查询子树和
{
    return query(1,id[u],id[u]+sz[u]-1);
}
signed main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
        cin>>w[i];
    memset(h,-1,sizeof h);
    for(int i=1;i<n;i++)
    {
        int a,b;
        cin>>a>>b;
        add(a,b);add(b,a);
    }
    dfs1(1,-1,1);
    dfs2(1,1);
    build(1,1,n);//建树
    cin>>m;
    while(m--)
    {
        int op,u,v,k;
        cin>>op>>u;
        if(op==1)
        {
            cin>>v>>k;
            update_path(u,v,k);
        }
        else if(op==2)
        {
            cin>>k;
            update_tree(u,k);
        }
        else if(op==3)
        {
            cin>>v;
            cout<<query_path(u,v)<<endl;
        }
        else
            cout<<query_tree(u)<<endl;
    }
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值