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

本文共 2774 字,大约阅读时间需要 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 long
    int 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/

    你可能感兴趣的文章
    nrf开发笔记一开发软件
    查看>>
    NS3 IP首部校验和
    查看>>
    NSDateFormatter的替代方法
    查看>>
    NSError 的使用方法
    查看>>
    nsis 安装脚本示例(转)
    查看>>
    NSJSON的用法(oc系统自带的解析方法)
    查看>>
    nslookup 的基本知识与命令详解
    查看>>
    NSOperation基本操作
    查看>>
    NSRange 范围
    查看>>
    NSSet集合 无序的 不能重复的
    查看>>
    NSURLSession下载和断点续传
    查看>>
    NSUserdefault读书笔记
    查看>>
    NT AUTHORITY\NETWORK SERVICE 权限问题
    查看>>
    NT symbols are incorrect, please fix symbols
    查看>>
    ntelliJ IDEA 报错:找不到包或者找不到符号
    查看>>
    ntko web firefox跨浏览器插件_深度比较:2019年6个最好的跨浏览器测试工具
    查看>>
    ntko文件存取错误_苹果推送 macOS 10.15.4:iCloud 云盘文件夹共享终于来了
    查看>>
    ntpdate 通过外网同步时间
    查看>>
    NTPD使用/etc/ntp.conf配置时钟同步详解
    查看>>
    NTP及Chrony时间同步服务设置
    查看>>