当前位置:首页 > 技术 > 正文内容

树链剖分详解与应用

访客 技术 2026年6月21日 1

基本概念

树链剖分是一种将树形结构转化为链式结构的技术,能够高效处理树上路径和子树相关操作。

树链剖分示意图

核心定义包括:

  • 重儿子:对于非叶节点,子树规模最大的子节点
  • 轻儿子:除重儿子外的所有子节点
  • 重边:连接重儿子与其父节点的边
  • 重链:由连续重边构成的路径
  • 轻边:非重边的边
  • 链头:重链中深度最小的节点

工作原理

树链剖分的关键性质:任意节点到根节点的路径上,经过的重链数量和轻边数量都不超过 O(log n)。

这一性质的证明基于二叉树模型:跨越轻边时,子树规模至少减半,因此最多经过 log n 条轻边。

LCA计算优化

利用树链剖分求最近公共祖先:

  1. 当两节点不在同一重链时,深度较大的链头向上跳跃
  2. 当两节点位于同一重链时,深度较小的即为LCA
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e6 + 10;
int depth[N], chainHead[N], parent[N], subtreeSize[N], heavySon[N];
vector<int> tree[N];

void computeSubtree(int node) {
    subtreeSize[node] = 1;
    depth[node] = depth[parent[node]] + 1;
    
    for (int child : tree[node]) {
        if (child == parent[node]) continue;
        parent[child] = node;
        computeSubtree(child);
        subtreeSize[node] += subtreeSize[child];
        
        if (subtreeSize[child] > subtreeSize[heavySon[node]]) {
            heavySon[node] = child;
        }
    }
}

void assignChain(int node, int head) {
    chainHead[node] = head;
    if (heavySon[node]) {
        assignChain(heavySon[node], head);
    }
    
    for (int child : tree[node]) {
        if (child == parent[node] || child == heavySon[node]) continue;
        assignChain(child, child);
    }
}

int findLCA(int a, int b) {
    while (chainHead[a] != chainHead[b]) {
        if (depth[chainHead[a]] > depth[chainHead[b]]) {
            a = parent[chainHead[a]];
        } else {
            b = parent[chainHead[b]];
        }
    }
    return depth[a] < depth[b] ? a : b;
}

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    
    int nodes, queries, root;
    cin >> nodes >> queries >> root;
    
    for (int i = 1; i < nodes; i++) {
        int u, v;
        cin >> u >> v;
        tree[u].push_back(v);
        tree[v].push_back(u);
    }
    
    computeSubtree(root);
    assignChain(root, root);
    
    while (queries--) {
        int a, b;
        cin >> a >> b;
        cout << findLCA(a, b) << '\n';
    }
    return 0;
}

高级应用:路径与子树操作

树链剖分结合线段树可高效处理以下操作:

  • 路径节点值修改与查询
  • 子树节点值修改与查询
DFS序特性

关键在于DFS序的连续性:重链节点在线段树中位置连续,子树节点同样连续。

#include <bits/stdc++.h>
#define int long long
#define LEFT (index << 1)
#define RIGHT (index << 1 | 1)
using namespace std;

const int MAXN = 1e6 + 10;
int MOD, nodeCount, operationCount, root, timestamp;
int initialValues[MAXN], mappedValues[MAXN], position[MAXN];
int size[MAXN], level[MAXN], heavyChild[MAXN], father[MAXN], chainTop[MAXN];
vector<int> adj[MAXN];

struct SegmentNode {
    int left, right, value, sum, lazy;
} segTree[MAXN];

void updateUp(int index) {
    segTree[index].sum = (segTree[LEFT].sum + segTree[RIGHT].sum) % MOD;
}

void propagateDown(int index) {
    if (segTree[index].lazy) {
        segTree[LEFT].sum = (segTree[LEFT].sum + (segTree[LEFT].right - segTree[LEFT].left + 1) * segTree[index].lazy % MOD) % MOD;
        segTree[RIGHT].sum = (segTree[RIGHT].sum + (segTree[RIGHT].right - segTree[RIGHT].left + 1) * segTree[index].lazy % MOD) % MOD;
        segTree[LEFT].lazy = (segTree[LEFT].lazy + segTree[index].lazy) % MOD;
        segTree[RIGHT].lazy = (segTree[RIGHT].lazy + segTree[index].lazy) % MOD;
        segTree[index].lazy = 0;
    }
}

void buildTree(int index, int l, int r) {
    segTree[index] = {l, r, 0, 0, 0};
    if (l == r) {
        segTree[index].sum = mappedValues[l];
        return;
    }
    int mid = (l + r) >> 1;
    buildTree(LEFT, l, mid);
    buildTree(RIGHT, mid + 1, r);
    updateUp(index);
}

void rangeAdd(int index, int l, int r, int delta) {
    if (segTree[index].left >= l && segTree[index].right <= r) {
        segTree[index].sum = (segTree[index].sum + (segTree[index].right - segTree[index].left + 1) * delta % MOD) % MOD;
        segTree[index].lazy = (segTree[index].lazy + delta) % MOD;
        return;
    }
    propagateDown(index);
    int mid = (segTree[index].left + segTree[index].right) >> 1;
    if (l <= mid) rangeAdd(LEFT, l, r, delta);
    if (r > mid) rangeAdd(RIGHT, l, r, delta);
    updateUp(index);
}

int rangeQuery(int index, int l, int r) {
    if (segTree[index].left >= l && segTree[index].right <= r) {
        return segTree[index].sum;
    }
    propagateDown(index);
    int mid = (segTree[index].left + segTree[index].right) >> 1;
    int result = 0;
    if (l <= mid) result = (result + rangeQuery(LEFT, l, r)) % MOD;
    if (r > mid) result = (result + rangeQuery(RIGHT, l, r)) % MOD;
    return result;
}

void firstDFS(int node) {
    size[node] = 1;
    level[node] = level[father[node]] + 1;
    
    for (int child : adj[node]) {
        if (child == father[node]) continue;
        father[child] = node;
        firstDFS(child);
        size[node] += size[child];
        if (size[child] > size[heavyChild[node]]) {
            heavyChild[node] = child;
        }
    }
}

void secondDFS(int node, int chainStart) {
    chainTop[node] = chainStart;
    position[node] = ++timestamp;
    mappedValues[timestamp] = initialValues[node];
    
    if (heavyChild[node]) {
        secondDFS(heavyChild[node], chainStart);
    }
    
    for (int child : adj[node]) {
        if (child == father[node] || child == heavyChild[node]) continue;
        secondDFS(child, child);
    }
}

void pathUpdate(int x, int y, int value) {
    while (chainTop[x] != chainTop[y]) {
        if (level[chainTop[x]] < level[chainTop[y]]) swap(x, y);
        rangeAdd(1, position[chainTop[x]], position[x], value);
        x = father[chainTop[x]];
    }
    if (level[x] > level[y]) swap(x, y);
    rangeAdd(1, position[x], position[y], value);
}

int pathQuery(int x, int y) {
    int result = 0;
    while (chainTop[x] != chainTop[y]) {
        if (level[chainTop[x]] < level[chainTop[y]]) swap(x, y);
        result = (result + rangeQuery(1, position[chainTop[x]], position[x])) % MOD;
        x = father[chainTop[x]];
    }
    if (level[x] > level[y]) swap(x, y);
    result = (result + rangeQuery(1, position[x], position[y])) % MOD;
    return result;
}

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    
    cin >> nodeCount >> operationCount >> root >> MOD;
    for (int i = 1; i <= nodeCount; i++) cin >> initialValues[i];
    
    for (int i = 1; i < nodeCount; i++) {
        int u, v;
        cin >> u >> v;
        adj[u].push_back(v);
        adj[v].push_back(u);
    }
    
    firstDFS(root);
    secondDFS(root, root);
    buildTree(1, 1, nodeCount);
    
    while (operationCount--) {
        int type, x, y, z;
        cin >> type >> x;
        
        switch (type) {
            case 1:
                cin >> y >> z;
                pathUpdate(x, y, z);
                break;
            case 2:
                cin >> y;
                cout << pathQuery(x, y) << '\n';
                break;
            case 3:
                cin >> z;
                rangeAdd(1, position[x], position[x] + size[x] - 1, z);
                break;
            case 4:
                cout << rangeQuery(1, position[x], position[x] + size[x] - 1) << '\n';
                break;
        }
    }
    return 0;
}

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。