【学习】树链剖分

解决的问题:

把对树的操作转换到序列上,以便于用数据结构来维护。

基本概念:

  • 树链:就是树上的路径。
  • 剖分,就是把路径分类为重链轻链
  • 重儿子:siz[u]为v的子节点中siz值最大的,那么u就是v的重儿子。
  • 轻儿子:v的其它子节点。
  • 重边:点v与其重儿子的连边。
  • 轻边:点v与其轻儿子的连边。
  • 重链:由重边连成的路径。
  • 轻链:轻边。

需要处理的数组含义:

  • siz[v]表示以v为根的子树的节点数。
  • dep[v]表示v的深度(根深度为1)。
  • fa[v]表示v的父亲。
  • son[v]表示v的重儿子。
  • top[v]表示v所在的重链的顶端节点。
  • pos[v]表示v在线段树中的位置。

算法流程:

第一次dfs:

把fa,dep,siz,son求出来。

// in main() -> dep[1] = 1, dfs1(1);
void dfs1(int now){
    int mx1 = 0, mx2 = 0;
    size[now] = 1;
    for(int i = head[now]; i; i = next[i]){
        if(to[i] == fa[now]) continue;
        fa[to[i]] = now, dep[to[i]] = dep[now] + 1;
        dfs1(to[i]);
        size[now] += size[to[i]];
        if(mx1 < size[to[i]]) mx1 = size[to[i]], mx2 = to[i];
    }
    son[now] = mx2;
}

第二次dfs:

把top,pos求出来。
具体的,ž根节点为起点,向下拓展构建重链,优先选择重儿子进行dfs,完成之后在更新轻儿子。

// in main() -> top[1] = 1, dfs2(1);
void dfs2(int now){
    pos[now] = ++ ct;
    if(son[now] != 0) top[son[now]] = top[now], dfs2(son[now]);
    for(int i = head[now]; i; i = next[i]){
        if(to[i] == son[now] || to[i] == fa[now]) continue;
        top[to[i]] = to[i], dfs2(to[i]);
    }
}

建立数据结构(以线段树为例)

// in main() -> for(int i = 1; i <= n; i ++) v[pos[i]] = val[i]; -> 反射节点
// in main() -> build(1, 1, n);
void build(int now, int l, int r){
    if(l == r){t_max[now] = v[l]; return;}
    build(lch, l, mid);
    build(rch, mid+1, r);
    t_max[now] = std::max(t_max[lch], t_max[rch]);
}

查询:

int que_max(int x, int y){
    int res = -inf;
    while(top[x] != top[y]){ // 一直跳到同一条链上
        if(dep[top[x]] < dep[top[y]]) std::swap(x, y); // 每次走深的一个
        res = std::max(res, que_max(1, 1, n, pos[top[x]], pos[x]));
        x = fa[top[x]]; // 到上一个链上去
    }
    if(pos[x] > pos[y]) std::swap(x, y); // 处理在同一条链上的情况
    res = std::max(res, que_max(1, 1, n, pos[x], pos[y]));
    return res;
}

时间复杂度:

树链剖分有一个很强的性质:从任意一个叶子节点出发,到根节点的路径上最多有 logn 条重链。
所以再计算上线段树区间查询的复杂度 logn ,总的时间复杂度为 log2n

SDOI必备模板(非递归版)

void pre(){
    int l = 1, r = 0; tp = 0;
    q[++ r] = 1, dep[1] = 1;
    while(l <= r){
        int x = q[l ++]; sz[x] = 1;
        for(int i = head[x]; i; i = nxt[i]){
            uint u = to[i]; if(u == fa[x]) continue;
            dep[u] = dep[x] + 1, fa[u] = x, q[++ r] = u;
        }
    }
    for(int i = n; i >= 1; i --){
        int x = q[i]; if(!fa[x]) continue;
        sz[fa[x]] += sz[x];
        if(sz[son[fa[x]]] < sz[x]) son[fa[x]] = x;
    }
    st[++ tp] = 1, top[1] = 1;
    while(tp){
        int x = st[tp --]; pos[x] = ++ tim, num[tim] = x;
        for(int i = head[x]; i; i = nxt[i]){
            uint u = to[i]; if(u == fa[x] || u == son[x]) continue;
            st[++ tp] = u, top[u]  = u;
        }
        if(son[x]) st[++ tp] = son[x], top[son[x]] = top[x];
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值