树链剖分笔记

什么是树链剖分?树链剖分有什么作用?

字面意思,就是把一颗树拆成几个链,这些链上的节点序号是连续的,那么对于树上的路径的一些问题就可以转化为区间问题,就可以用熟悉的一些数据结构来维护了

一些需要知道的定义

重/轻 儿子:对于父节点来说,他的儿子节点中size最大的就是重儿子其他都是轻儿子,如果所有儿子节点size都相同,那么任意选一个作为重儿子
重/轻边父节点与他重儿子连的边是重边,其余是轻边
重/轻链,重边连起来的就是重链

推出的性质

1.如果u与v是一条轻边,那么size[v]*2<size[u]
2.任意两点之间的路径最多包含logn段重链

代码中需要的数组:

int w[N],fa[N],son[N],dep[N],top[N];
int sz[N],nw[N],id[N];
vector<int> ed[N];

其中w是最初的权值,fa是父节点,son是重儿子,dep是深度,top是链的顶点,sz是大小,id是新的序号,nw是新的序号对应的权值

我们可以用两次dfs来求出这些信息

void dfs1(int u,int father,int depth)   //求重儿子
{
    dep[u]=depth,fa[u]=father,sz[u]=1;
    for(auto x:ed[u])
    {
        if(x==father) continue;
        dfs1(x,u,depth+1);
        sz[u]+=sz[x];
        if(sz[son[u]]<sz[x]) son[u]=x;//如果节点sze大于重儿子,那么重儿子就是新的节点
    }
}
void dfs2(int u,int t) //t代表的是当前重链的顶点
{
    id[u]=++cnt,nw[cnt]=w[u],top[u]=t;
    if(!son[u]) return ;
    dfs2(son[u],t);//对于重儿子来说,top仍然是现在的top
    for(auto x:ed[u])
    {
        if(x==fa[u]||x==son[u]) continue;
        dfs2(x,x);//对于轻儿子,轻链所在的顶点就是自己
    }
}

线段树的经典操作

struct stree{
    int l,r;
    ll sum,add;
}st[N<<2];
void pushup(int u)
{
    st[u].sum=st[u<<1].sum+st[u<<1|1].sum;
}
void pushdown(int u)
{
    if(st[u].add)
    {
        st[u<<1].sum+=1ll*st[u].add*(st[u<<1].r-st[u<<1].l+1);
        st[u<<1|1].sum+=1ll*st[u].add*(st[u<<1|1].r-st[u<<1|1].l+1);
        st[u<<1].add+=st[u].add,st[u<<1|1].add+=st[u].add;
        st[u].add=0;
    }
}
void build(int u,int l,int r)
{
    st[u]={l,r,0,0};
    if(l==r) st[u].sum=nw[l];
    else
    {
        int mid=l+r>>1;
        build(u<<1,l,mid),build(u<<1|1,mid+1,r);
        pushup(u);
    }
}
void modify(int u,int l,int r,int k)
{
    if(st[u].l>=l&&st[u].r<=r) st[u].sum+=1ll*k*(st[u].r-st[u].l+1),st[u].add+=k;
    else
    {
        pushdown(u);
        int mid=st[u].l+st[u].r>>1;
        if(l<=mid) modify(u<<1,l,r,k);
        if(r>mid) modify(u<<1|1,l,r,k);
        pushup(u);
    }
}
ll query(int u,int l,int r)
{
    if(st[u].l>=l&&st[u].r<=r) return st[u].sum;
    ll res=0;
    pushdown(u);
    int mid=st[u].l+st[u].r>>1;
    if(l<=mid) res+=query(u<<1,l,r);
    if(r>mid) res+=query(u<<1|1,l,r);
    return res;
}

剩下的就是一个问题了:怎样求出两点之间的所有重链呢?

比较像倍增求lca问题,当两个节点没有处于同一条链的时候,就先取出链的top的dep较大的那一条链,也就是先把下边的链取出,然后开始向根节点爬,直到他俩在一条链里
对于修改一个子树的情况,就要简单很多,父节点的dfs序一定是最小的,对于这一颗子树,他的序号都是连续的,所以只需要加上sze就可以
void update_path(int u,int v,int k)
{
    while(top[u]!=top[v])
    {
        if(dep[top[u]]<dep[top[v]]) swap(u,v);
        modify(1,id[top[u]],id[u],k);
        u=fa[top[u]];
    }
    if(dep[u]<dep[v]) swap(u,v);
    modify(1,id[v],id[u],k);
}
void update_tree(int u,int k)
{
    modify(1,id[u],id[u]+sz[u]-1,k);
}
ll query_path(int u,int v)
{
    ll 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;
}
ll query_tree(int u)
{
    return query(1,id[u],id[u]+sz[u]-1);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值