当前位置: 首页 > news >正文

树链剖分详解(长链剖分)

参考资料

例题:P5903 树上 \(k\) 级祖先。

例题:CF1009F Dominant Indices。

因为当时写树链剖分详解(重链剖分)时并不会长链剖分,所以只能另开一文了。


长链剖分」,除「重链剖分」外的另一种树剖方式,与深度相关。

长链剖分

定义「重子节点」表示子树深度最大的子节点,「轻子节点」表示剩余子节点。其余定义同「重链剖分」的定义。(但是也有人称之为「长子节点」和「短子节点」)

长链剖分可以容易地类似于重链剖分实现,只需要在寻找重子节点时稍作修改即可。

性质

没什么用的性质。

从任意节点出发往上跳,跳轻边的次数为 \(\mathcal O\left(\sqrt n\right)\)。也就是说,长链数量为 \(\mathcal O\left(\sqrt n\right)\)

也就是说,使用长链剖分可以得到一个单次操作复杂度 \(\mathcal O\left(\sqrt n\log n\right)\) 的算法。

但是长链剖分的实际应用并不是代替重链剖分,而是维护一些与「深度」相关的信息。

简单应用

给定一棵树,确定每一个点 \(x\) 的子树内各深度的子节点数量。要求时间复杂度为线性。(重链剖分配合 DSU on tree可以做到 \(\mathcal O(n\log n)\)

维护信息与深度正相关,点 \(x\) 的信息量为 \(\mathcal O(\textit{depth}_x)\),考虑长链剖分。

\(x\) 可以 \(\mathcal O(1)\) 继承重子节点的信息,其余轻子节点 \(i\)\(\mathcal O(\textit{depth}_i)\) 的代价暴力继承信息即可。

此时时间复杂度为 \(\mathcal O(n)\)。因为每一条长链都只会被统计一次。

维护诸如此类的深度相关信息,均可以类似地做到 \(\mathcal O(n)\)

长链剖分求解树上 \(k\) 级祖先

例题:P5903 树上 \(k\) 级祖先。


给定一棵 \(n\) 个点的有根树,\(q\) 次询问,每次询问点 \(x\)\(k\) 级祖先。

长链剖分优化 DP

例题:CF1009F Dominant Indices。


给定一棵以 \(1\) 作为根的 \(n\) 个点的有根树。

定义 \(d_{x,k}\) 表示 \(x\)\(k\) 级子节点的数量。对于每一个点 \(x\),求最小的 \(k\) 满足 \(d_{x,k}\) 最大。钦定 \(d_{x,0}=1\)

\(v_x\) 表示 \(x\) 的子节点集,有:

\[d_{x,k}=\sum_{i\in v_x}d_{x,k-1} \]

暴力维护会 TLE 并且 MLE。注意到 DP 是与深度相关的,考虑长链剖分。

考虑如何 \(\mathcal O(1)\) 继承重子节点信息。可以采用指针。具体而言,将 \(x\) 的重子节点 \(\textit{son}_x\) 的信息 \(d_{\textit{son}_x,0},d_{\textit{son}_x,1},\cdots,d_{\textit{son}_x,k}\) 直接映射\(d_{x,1},d_{x,2},\cdots,d_{x,k+1}\) 上即可。

这样,每一条长链对应一组存储的 \(d\),总空间复杂度为 \(\mathcal O(n)\)。时间复杂度也为 \(\mathcal O(n)\)

同时需要时刻维护当前序列 \(d\) 的最大值及其下标。

参考代码
//#include<bits/stdc++.h>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<iomanip>
#include<cstdio>
#include<string>
#include<vector>
#include<cmath>
#include<ctime>
#include<deque>
#include<queue>
#include<stack>
#include<list>
using namespace std;
constexpr const int N=1e6;
int n,ans[N+1];
vector<int>g[N+1];
struct d{int max,id;vector<int>a;d(){max=1;id=0;} int size(){return a.size();}void resize(int x){a.resize(x);}int& operator [](int x){return a[x];}
}d[N+1];
namespace hld{int depth[N+1],maxDepth[N+1],son[N+1];void dfs1(int x,int fx){depth[x]=depth[fx]+1;maxDepth[x]=depth[x];for(int i:g[x]){if(i==fx){continue;}dfs1(i,x);maxDepth[x]=max(maxDepth[x],maxDepth[i]);if(maxDepth[i]>maxDepth[son[x]]){son[x]=i;}}}int cnt; void dfs2(int x,int fx,int id,int pre){if(d[id].size()<pre+1){d[id].resize(pre+1);}d[id][pre]=1;if(son[x]){dfs2(son[x],x,id,pre+1);}for(int i:g[x]){if(i==son[x]||i==fx){continue;}int id2=++cnt;dfs2(i,x,id2,0);if(d[id].size()<pre+1+d[id2].size()){d[id].resize(pre+1+d[id2].size());}for(int j=0;j<d[id2].size();j++){d[id][pre+1+j]+=d[id2][j];if(d[id][pre+1+j]>d[id].max){d[id].max=d[id][pre+1+j];d[id].id=pre+1+j;}else if(d[id][pre+1+j]==d[id].max){d[id].id=min(d[id].id,pre+1+j);}}}ans[x]=max(d[id].id-pre,0);//若小于 0,则说明为 pre。}int main(){dfs1(1,0);cnt=1;dfs2(1,0,1,0);return 0;}
}
int main(){/*freopen("test.in","r",stdin);freopen("test.out","w",stdout);*/ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);cin>>n;for(int i=1;i<n;i++){int u,v;cin>>u>>v;g[u].push_back(v);g[v].push_back(u);}hld::main();for(int i=1;i<=n;i++){cout<<ans[i]<<'\n';}cout.flush();/*fclose(stdin);fclose(stdout);*/return 0;
}
http://www.sczhlp.com/news/11435/

相关文章:

  • 圆锥曲线二级结论
  • 新版EIDE创建C51_with_keil5模板方法
  • 【日记】2025-8-13
  • 谷歌账号停用申诉 google账户被封如何解封 如何填写申诉理由和找回账号
  • CompletableFuture
  • 大東聰明家App技术支持
  • 【碎碎念】无题
  • 联想Lenovo R7000P-2025款 安装 Ubuntu linux 后没有 mt7925 网卡驱动(网卡不能正常运行或无法识别)的解决方案
  • 【LeetCode 199】力扣算法:二叉树的右视图
  • SPI与菊花链
  • Java集合——9.使用Set
  • vue基础
  • 目标使用过期的TLS1.0 版协议与目标主机支持RSA密钥交换 漏洞修复 系统:windows10 - L-+*
  • python虚拟环境和包管理工具Pipenv详解
  • PG系列:pg_probackup的时间点恢复失败案例分析
  • OOP:构造方法
  • 日程管理功能的后端开源组件精选方案
  • 软考系统分析师每日学习卡 | [日期:2025-08-13] | [今日主题:逻辑结构设计]
  • MySQL查询表结构、表大小
  • xUnit 单元测试:如何构造可共享的测试实例?
  • 苹果紧急修复针对Chrome用户的零日漏洞
  • 一文详解2025年医疗器械CRM怎么选?(附选型要素、实施指南、服务商推荐、案例解读) - SaaS软件
  • 双重计数
  • 用块状数组求解区间众数问题
  • Java抛出异常
  • Java IO流
  • python虚拟环境详解
  • 在AI技术快速落地的时代,挖掘真实需求成为关键——某知名Go SDK框架需求洞察
  • 一步一步学习使用LiveBindings(12) LiveBindings与具有动态呈现的TListView
  • Project 2024 专业版增强版下载安装和激活教程(附安装包)