快速理解树链剖分

初阶

关键在于处理如何处理链的信息。

前置知识

首先我们定义轻重链:
孩子结点的子树最大的作为重儿子,每次优先跑重儿子,可以得到一条重链。
重链的 d f s dfs dfs序显然是连续的,轻链就是自己。
【可以证明从根结点到叶子节点的路径上,轻重链的个数不超过 l o g n logn logn,但我不会】
在这里插入图片描述
灰色是原来边,红色是重链,黑色是轻链,轻链我个人觉得需要记录的只有叶子节点。因为很明显可以发现 2 → 3 2\to 3 23这条轻链的两个点都在别的链上可以记录,所以接下来我们可以认为轻链只有自己, 2 → 3 2\to 3 23这种可以当做没有。

这就代表我们可以通过这个做文章了。
对于两个结点,我们要对其树上路径进行处理,就是把这些路径分成多个链进行处理,因为链都是连续的,所以可以直接当线性结构做(线段树等)


具体操作

具体怎么实现分成多个链呢,我们从头开始处理:
1、首先得到重儿子

void dfs1(int u,int pre){
    dep[u]=dep[pre]+1;
    sz[u]=1,fa[u]=pre;
    int s=0;
    for(auto v:G[u]){
        if(v==pre)continue;
        dfs1(v,u);
        sz[u]+=sz[v];
        if(sz[v]>s)son[u]=v,s=sz[v];
    }
}

2、通过重儿子跑出重链,对于每个点存下自己所在链的链头 t o p [ i ] top[i] top[i],轻链就是自己

void dfs2(int u,int tr_top){
    dfn[u]=++cnt;
    top[u]=tr_top;
    if(son[u])dfs2(son[u],tr_top);
    for(auto v:G[u]){
        if(v==fa[u]||v==son[u])continue;
        dfs2(v,v);
    }
}

3、对于两个结点,分别往上跳,每次跳的时候先跳链头深度低的,最后在一条链的时候结束。先跳低的能保证不会直接跳出 l c a lca lca的位置,因为只有重链会跳出去,而一个是重链,另一个必然是轻链(或者链头深度低的重链),所以会先跳另一个,直到跳到这个重链上

int query(int l,int r,int id){
    int ans=0;
    while(top[l]!=top[r]){
        if(dep[top[l]]<dep[top[r]])swap(l,r);
        ans+=query(dfn[top[l]],dfn[l],now);
        l=fa[top[l]];
    }
    if(dfn[l]<=dfn[r])ans+=query(dfn[l],dfn[r],now);
    else ans+=query(dfn[l],dfn[r],now);
    return ans;
}

这里的 q u e r y query query就是具体操作了,query换成更新操作也是一样的,因为这段操作实际上只是将路径拆分成链而已。

例题

牛客练习赛 F F F: 题解

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值