博客
关于我
[学习笔记] 长链剖分
阅读量:410 次
发布时间:2019-03-05

本文共 2722 字,大约阅读时间需要 9 分钟。

轻重链剖分是一种与传统链剖分相似的数据结构,它的核心区别在于对“重儿子”的定义进行了调整。传统链剖分中,重儿子的定义是“所在子树中的最大儿子”,而轻重链剖分中,重儿子则定义为“所在子树中的最深儿子”。这种改变使得轻重链剖分在处理与深度相关的问题时更加灵活,特别是在优化动态规划(DP)的过程中应用广泛。

性质解析

轻重链剖分具有以下几个重要性质:

  • 链长度和性质

    所有链的长度总和为 (O(n)) 级别。这意味着在预处理和查询时,算法的时间复杂度主要取决于节点数量,而不会因为链的长度过长而变得不可控。

  • 祖先深度性质

    任意一个点的 (k) 次祖先 (y) 所在的长链的长度至少为 (k)。这对于快速定位特定祖先节点非常有用,特别是在需要处理多个层次查询时。

  • 跳跃次数限制

    任何一个点向上跳跃重链的次数不会超过 (\sqrt{n}) 次。这限制了算法在跳跃过程中的复杂度,确保在大数据量下依然能够高效运行。

  • 应用场景

    轻重链剖分在多个领域中有广泛应用,其中最为突出的两种应用是计算 (k) 次祖先和优化动态规划问题。

    一、计算k次祖先

    为了快速定位一个节点的 (k) 次祖先,轻重链剖分采取了以下预处理步骤:

  • 链头记录:记录每个节点的链头及其深度,时间复杂度为 (O(n))。
  • 倍增处理:预处理每个节点的 (2^k) 次祖先,通过逐层倍增的方式,预处理时间为 (O(n \log n))。
  • 链元素记录:对于长度较长的链,记录链头向上和向下的部分信息,确保查询时能够快速获取所需祖先。
  • 二进制处理:记录每个节点的二进制最高位信息,用于快速定位跳跃步数。
  • 算法的具体步骤如下:

  • 跳跃处理:首先利用倍增数组跳过 (k) 的最高位,剩余的步数记为 (k'),其中 (k' < \frac{k}{2})。
  • 祖先定位:利用预处理的链头和深度信息,确定当前节点的长链长度是否大于等于 (k'),进而通过向上或向下的数组快速获取 (k) 次祖先。
  • 这种方法的预处理复杂度为 (O(n \log n)),而单次查询仅需 (O(1)) 时间,极大提升了效率。

    二、优化动态规划

    在动态规划优化方面,轻重链剖分的应用尤为突出。传统的DP优化方法通常面临复杂度瓶颈,而轻重链剖分通过链结构的特性,能够显著降低优化时间。

    以一个典型问题为例,假设需要计算树上满足特定深度条件的节点对数。传统方法可能需要暴力遍历所有子树,复杂度为 (O(n^2)),而轻重链剖分可以将其优化为 (O(n))。

    具体实现步骤如下:

  • 定义函数:设 (f(i, j)) 为节点 (i) 子树内深度为 (j) 的节点数,(g(i, j)) 为满足特定深度条件的无序数对 ((i, j)) 的个数。
  • 转移方程
    • (g(i, j) = \sum_{x < y} f(x, j-1) \times f(y, j-1))
    • (g(i, j) = \sum g(x, j+1))
    • (f(i, j) = \sum f(i, j-1))
  • 预处理和查询:通过轻重链剖分预处理链头和深度信息,快速定位特定深度节点,实现高效的DP优化。
  • 这种方法充分利用了轻重链剖分的优势,将传统 (O(n^2)) 的复杂度降低至 (O(n)),极大提升了算法效率。

    代码示例

    #include 
    const int M = 100005;#define int long longint read() { int x = 0, f = 1; char c; while ((c = getchar()) < '0' || c > '9') { if (c == '-') f = -1; } while (c >= '0' && c <= '9') { x = (x < 3 ? x << 1 : x << 1) + (c - '0'); c = getchar(); } return x * f;}int n, tot, F[M], d[M], dep[M], son[M];int *f = (int *)malloc(M), *g = (int *)malloc(M), p[4 * M], *o = p, ans;struct edge { int v, next; edge(int v = 0, int N = 0) : v(V), next(N) {}} e[2 * M];void pre(int u, int fa) { d[u] = d[fa] + 1; for (int i = F[u]; i; i = e[i].next) { int v = e[i].v; if (v == fa) continue; pre(v, u); if (dep[v] > dep[son[u]]) son[u] = v; } dep[u] = dep[son[u]] + 1;}void dfs(int u, int fa) { if (son[u]) { f[son[u]] = f[u] + 1; g[son[u]] = g[u] - 1; dfs(son[u], u); } f[u][0] = 1; ans += g[u][0]; for (int i = F[u]; i; i = e[i].next) { int v = e[i].v; if (v == fa || v == son[u]) continue; f[v] = o; o += dep[v] * 2; g[v] = o; o += dep[v] * 2; dfs(v, u); for (int j = 0; j < 32; j++) { if (dep[v] & (1 << j)) { p[j] = v; } } }}

    总结

    轻重链剖分通过对重儿子定义的调整,使其在深度相关问题中的应用更加灵活和高效。其预处理和查询的复杂度显著优于传统方法,广泛应用于 (k) 次祖先查询和动态规划优化等领域。如果需要更深入的理解和应用,可以通过实践和练习将这些理论转化为具体的算法实现。

    转载地址:http://pjozz.baihongyu.com/

    你可能感兴趣的文章
    nginx 常用配置记录
    查看>>
    Nginx 我们必须知道的那些事
    查看>>
    nginx 配置~~~本身就是一个静态资源的服务器
    查看>>
    Nio ByteBuffer组件读写指针切换原理与常用方法
    查看>>
    NLP 基于kashgari和BERT实现中文命名实体识别(NER)
    查看>>
    No 'Access-Control-Allow-Origin' header is present on the requested resource.
    查看>>
    nullnullHuge Pages
    查看>>
    Numpy如何使用np.umprod重写range函数中i的python
    查看>>
    oauth2-shiro 添加 redis 实现版本
    查看>>
    OAuth2.0_JWT令牌-生成令牌和校验令牌_Spring Security OAuth2.0认证授权---springcloud工作笔记148
    查看>>
    OAuth2.0_授权服务配置_Spring Security OAuth2.0认证授权---springcloud工作笔记140
    查看>>
    OAuth2.0_授权服务配置_资源服务测试_Spring Security OAuth2.0认证授权---springcloud工作笔记146
    查看>>
    OAuth2.0_环境搭建_Spring Security OAuth2.0认证授权---springcloud工作笔记139
    查看>>
    object detection错误之Could not create cudnn handle: CUDNN_STATUS_INTERNAL_ERROR
    查看>>
    Objective-C享元模式(Flyweight)
    查看>>
    Objective-C以递归的方式实现二叉搜索树算法(附完整源码)
    查看>>
    Objective-C实现1000 位斐波那契数算法(附完整源码)
    查看>>
    Objective-C实现2 个数字之间的算术几何平均值算法(附完整源码)
    查看>>
    Objective-C实现3n+1猜想(附完整源码)
    查看>>
    Objective-C实现A-Star算法(附完整源码)
    查看>>