树相关算法与问题

算法或数据结构

无环连通图

性质:

  • 无环
  • 联通
  • \(|E|=|V|-1\)
  • \(\sum d_u = 2|E| = 2n - 2\)

基环树

我都不知道要不要放在这

有一个环的树,一般把环提出来当作根。
dp可以先跑树上的,再跑环上的。

  • 有环
  • 联通
  • \(|E|=|V|\)
  • \(\sum d_u = 2|E| = 2n\)

以及特殊内向基环树

  • \((\forall u)(d_u = 1)\)

仙人掌

罪大恶极

这种只有**出题人才会出的**缝合怪就不讲了。

重标号算法

瞎命名的(
通过将树上节点通过重新标号等方式将树上问题转化成序列问题。

dfs序(dfn)

\(dfs\) 过程中访问节点先后形成。
可以通过改变儿子节点访问顺序而形成不同性质的 \(dfn\)

一般 dfs 序

可以通过在选择儿子时不特殊考虑,随机选择一个儿子进行遍历得到。

性质:

  • \((\forall v\in son_u)(dfn_u \leq dfn_v)\),即父亲节点的 \(dfn\) 小于儿子

  • 子树内 \(dfn\) 连续,即形成一段区间。

问题:

树上子树修改
给定一颗 \(n\) 点树,\(q\) 次进行如下操作之一:

  • 将节点 \(u\) 子树内所有节点的权值加 \(k\)
  • 查询节点 \(u\) 子树内所有节点的权值和。

可以通过 dfs序 + 区修区查数据结构 做到 \(\operatorname{O}(q\log n)\)

重链剖分

\(siz_u\)\(u\) 子树内点的个数,\(hson_u\)\(son_u\)\(siz\) 最大的节点。
\(hson_u\) 称为重儿子,其他儿子称为轻儿子
\(u\) 往重儿子的边成为重边,轻儿子的边成为轻边
多条重边首尾相接构成重链。
可以通过 \(dfs\) 时先访问 \(hson\) 来得到重链剖分的 \(dfn\)

性质:

  • 重链长度和为 \(\operatorname O(n)\)

  • 任意一条路径上的重链条数与轻边条数都为 \(\operatorname{O}(\log n)\)。(每经过一条轻边子树大小至少除以 \(2\)

  • 重链上 \(dfn\) 连续,即形成一段区间。

  • 所有轻儿子的 \(siz\) 和为 \(\operatorname{O}(n\log n)\)(这个性质可以通过交换枚举顺序证明,每个点到根节点的轻边个数为 \(\operatorname{O}(\log n)\)

问题:

树上链修改
给定一颗 \(n\) 点树,\(q\) 次进行如下操作之一:

  • 将节点 \(u\) 到节点 \(v\) 路径上所有节点的权值加 \(k\)
  • 查询节点 \(u\) 到节点 \(v\) 路径上所有节点的权值和。

由于有重链 \(dfn\) 连续且条数 \(\operatorname{O}(\log n)\)。可以把每次操作的路径拆分成 \(\operatorname{O}(\log n)\) 条重链,进行修改查询。
可以做到 \(\operatorname{O}(q\log^2 n)\)

树上维护凸包
懒的写了。懂啥意思就行

每个点维护一个代表子树所有点的凸包。
可以每次暴力将轻儿子的凸包合并进当前点答案中。就是我们说的 DSU on Tree 树上启发式合并。
所有轻儿子的 \(siz\) 和为 \(\operatorname O(n\log n)\) 的性质保证了复杂度。
总而言之,所有合并复杂度为 \(\operatorname O(\min(siza, sizb))\) 的操作可以直接暴力合并,复杂度也是对的。

长链剖分

\(h_u\)\(u\) 到其子树内最远叶子节点的距离,\(lson_u\)\(son_u\)\(h\) 最大的节点。
与重链剖分相同
\(lson_u\) 称为重儿子,其他儿子称为轻儿子
\(u\) 往重儿子的边成为重边,轻儿子的边成为轻边
多条重边首尾相接构成长链
可以通过 \(dfs\) 时先访问 \(lson\) 来得到重链剖分的 \(dfn\)

性质:

  • 长链长度和为 \(\operatorname O(n)\)
  • 长链上 \(dfn\) 连续,即形成一段区间。
  • 任意节点 \(k\) 级祖先的长链不小于 \(k\)
  • 任意节点到根节点路径中长链最多 \(\operatorname O(\sqrt n)\) 条。

问题:

O(1) k级祖先
给定一颗 \(n\) 点树,\(q\) 查询节点 \(u\)\(k\) 级祖先。

预处理出每个节点的 \(2^k\) 级祖先,以及每条链链顶沿链往下 \(k(k \leq len_i)\) 个节点,和往父亲 \(k(k \leq len_i)\) 个节点。\(len_i\) 为链 \(i\) 的长度。
对于一次询问 \((u, k)\),我们先跳到 \(u\)\(2^c\) 级祖先满足 \(2^c \leq k < 2^{c+1}\)。此时 \(u\) 所在链链长 \(2^c \leq len_i\)
剩余步数 \(k - 2^c < 2^c\),可以直接通过预处理的数组求得。

优化dp
\(f_{u,k}\)\(u\) 子树内到 \(u\) 距离为 \(k\) 的节点个数。
对每个点 \(u\) 求出 \(\max_kf_{u, k}\)

直接维护 \(f_{u, k}\)\(\operatorname O(n^2)\) 的。
将两个节点 \(v_1,v_2\) 的信息合并,复杂度是 \(O(\min(h_{v_1},h_{v_2}))\),所以考虑长链剖分。
如果我们将轻儿子的信息合并到重儿子内,只会产生轻儿子所在重链长度的复杂度。
所以对于重儿子继承之前信息,可以 \(O(\sum_{t\in T} h_t) = O(n)\) 求得答案。(\(T\) 是链顶集合)

括号序列

\(dfs\) 进入节点时添加 \((\),退出时添加 \()\),即可得到一棵树的括号序列。
一般将括号换成节点编号。

性质:

  • 序列长度 \(2n\),其中每个节点出现恰 \(2\) 次,所代表区间为其子树
  • 序列中一段区间,可以代表树上一条链,可以通过删除相邻相等节点,并加入 \(lca\) 得到。

问题:

静态树链颜色个数。

转括号序列 + 莫队可以做到 \(O(n\sqrt n)\)
其实这玩意叫做树上莫队(
几乎所有树上使用莫队都要用到括号序列。

欧拉序

与 dfs 序类似,只是 dfs 每次回溯到 \(u\) 时再将 \(u\) 计入序列中。
这样节点 \(u\) 在序列中会有出现多次,第一次和最后一次出现代表进入以及离开 \(u\) 的子树,其他位置分割了 \(u\) 的每个儿子的子树。

性质:

  • 每个节点出现次数为其度数,欧拉序列长度为 \(2n-1\)
  • \(\operatorname{lca}(u,v)\) 出现在 \(u, v\) 第一次出现位置间

问题:

O(1) lca
多次询问 u, v 两点的最近公共祖先

由于性质二,所以记录下每个节点的深度可以通过区间 RMQ,预处理 \(O(n\log n)\),查询 \(O(1)\) 的求 \(lca\)

prufer 序列

一种用于树计数的序列。
生成方法如下:每次删除编号最小的叶子节点,并将其父亲记录在序列末尾,直到最终剩余两个节点。

性质:

  • 一个长度为 \(n\)\(prufer\) 序列与一颗大小为 \(n+2\) 的树唯一对应。

树上差分

差分思想在树上的应用

可以把子树加单点查询问题转化成单点加子树查询之类。

树上倍增

\(f_{u, k}\) 代表 \(u\) 节点以及其 \(2^k -1\) 级祖先所代表的信息。

需要满足信息可以快速合并以快速求出 \(f\) 数组。

一般不修改。

性质:

  • 空间一般为 \(\operatorname{O}(n\log n)\)
  • 对于任意一条沿祖先方向长为 \(l\) 的链,可以拆分成大小递减,首尾相接的 \(\operatorname O(\log l)\) 个段

点分治与点分树

将重心提出为根做计算,所以通常在题目不要求树有根时使用,或者一些换根技巧也可以使用。

一般用于计算路径贡献,有时可以计算连通块

性质:

  • 点分治不超过 \(\operatorname O(\log n)\) 层,对应点分树树高不超过 \(\operatorname O(\log n)\)

  • 点分树所有节点子树和 \(\operatorname O(n\log n)\)

  • 任意两点 \((u, v)\) ,点分树中 \((u, v)\) 路径上所有点,必然在原树 \((u, v)\) 路径上。

求长为 k 的路径个数

将每条路径挂在其经过的最浅分治点处计算。

生成树

生成树是一颗树,其边集为原图边集的子集,点集为原图点集。

性质:

  • 生成树联通所有点。

最小生成树

一张图最小权值和的生成树
可以通过 kruskal 算法构建。

新加入一条边 \((u, v)\) 时,可以考虑删除 \((u, v)\) 路径上最大边,以得到另一个较小生成树。

性质:

  • 最小生成树是一张图最小的生成树。

二叉树

每个节点只有不超过 \(2\) 个儿子节点的特殊树。
其左侧儿子成为左儿子,右侧儿子成为右儿子

性质:

  • 一个节点最多两个儿子。
  • 完全二叉树树高 \(\operatorname O(\log n)\)

线段树

一颗完全二叉树,每个节点代表一段序列上的区间,其左右儿子的区间由父亲节点的区间分裂得到
一种利用二叉树性质的数据结构,实则多层分块

性质:

  • 修改 \(\operatorname O(\log n)\)
  • 查询 \(\operatorname O(\log n)\)
  • 空间 \(\operatorname O(n)\)
  • 维护信息要求有结合律,即 \((ab)c=a(bc)\),以及可快速合并。
  • 若支持区间修改则需要懒标记,要求修改信息有结合律,且可快速合并。

标记永久化

区间修改时不下传标记,而是查询时再乘路径上标记,被成为标记永久化。
在可持久化线段树上常用。

性质:

  • 要求修改信息有交换律,或者可以快速求逆。

可持久化

保留每次修改前的线段树,通过只新建改变的部分的节点以节省空间。
区间修改时使用标记永久化可节省空间,否则查询需要新建节点。

性质:

  • 空间 \(\operatorname O(q\log n)\)

势能线段树

构造一个函数,来证明线段树复杂度。

性质:

  • 若构造势能函数 \(f\),使得操作复杂度与 \(\Delta f\) 相关,则时间复杂度为 \(O(\max f)\)

区间开根

多次区间开根只会让值变小,最终值恒定为 \(1\),势能函数 \(f\) 为所有数变成 \(1\) 之前操作次数。
容易发现区间开根若只修改非 \(1\) 的数则每修改一个,会让 \(f\)\(1\),则复杂度没问题。

笛卡尔树

一颗二叉树,可以通过每次将区间内最值提出为父亲节点、左右儿子向分裂出的区间递归建出。

用于区间最值相关计算

性质:

  • 父亲节点的权值小于(或大于)儿子。
  • 父亲节点在原序列上下标大于左儿子子树中所有节点,小于右儿子子树中任意节点。

给定一个长为 \(n\) 的数组 \(a\)
\((r - l + 1)\min\limits_{l\leq i \leq r}a_i\) 的最大值。

建一颗最小值的笛卡尔树,遍历所有节点即可。

平衡树

一种二叉树,通过每次操作后动态平衡维持树高为 \(\operatorname O(\log n)\)
比线段树更为高级,额外支持插入删除平移反转等奇怪操作

性质:

  • 修改 \(\operatorname O(\log n)\)
  • 查询 \(\operatorname O(\log n)\)
  • 空间 \(\operatorname O(n)\)
  • 维护信息要求有结合律,即 \((ab)c=a(bc)\),以及可快速合并。
  • 若支持区间修改则需要懒标记,要求修改信息有结合律,且可快速合并。

更高级的平衡树(
支持动态连边删边,维护的实际上是森林。

性质:

  • 实链剖分的特殊性质:\(access\) 操作遍历虚边个数均摊 \(\operatorname O(q\log n)\)
    证明:重链剖分,将虚边分为轻重两种考虑,
    轻边:每次操作经过轻虚边最多 \(\operatorname O(\log n)\) 条。
    重边:最多增加 \(\operatorname O(\log n)\) 条重虚边,同时每条重虚边遍历后会变成实边

这代表我们可以暴力遍历虚边。

tarjan

利用图的 dfs 序将

虚树

在一棵树中,取出 \(k\) 个点以及其所有 \(lca\),将“相邻”的两点连边所构成的树。

  • \(n\) 个点构造的虚树大小 \(\operatorname{O}(n)\)

树分块

神秘的分块,略有耳闻,没用过

问题

LCA

最近公共祖先。

性质

  • 序列 \(a\) 的所有元素的最近公共祖先即为所有 \(\operatorname{lca}(a_i, a_{i+1})\)\(1\leq i < n\))中深度最小的节点。

两点 LCA

基本问题。

一颗 \(n\) 个节点的树,\(q\) 次询问两点 \(u, v\)\(lca\)

倍增 LCA

\(\operatorname O(n\log n)-\operatorname O(\log n)\)

较为朴素的一种方式。
预处理倍增数组 \(f_{u,k}\) 代表 \(u\) 节点的 \(2^k\) 级祖先,\(O(n\log n)\)
每次查询则先将两个节点跳到同深度,跳到 \(lca\) 处。\(O(n\log n)\)

重链剖分

\(\operatorname O(n)-\operatorname O(\log n)\)
两点 \(lca\) 为其路径上深度最浅的点,既然涉及到链,可以使用树链剖分。
通过不断让更深的节点向上跳重链,直到 \(top_u=top_v\),即 \(u, v\) 的链顶相同。
此时 \(u,v\) 为祖先关系,我们直接返回深度更浅的节点即可。
一条链上重链个数 \(\operatorname O(\log n)\),所以一次查询 \(\operatorname O(\log n)\)
预处理即重链剖分预处理,\(\operatorname O(n)\)

欧拉序列

\(\operatorname O(n\log n)-\operatorname O(1)\)
预处理树的欧拉序列以及节点深度,转化成 \(RMQ\) 问题。
\(RMQ\) 预处理 \(O(n\log n)\),查询 \(O(1)\)

四毛子算法

\(\operatorname O(n)-\operatorname O(1)\)

待补充

区间 LCA

一颗 \(n\) 个节点的树,\(q\) 次询问编号在 \([l, r]\) 内的节点的 \(lca\)

  • 序列 \(a\) 的所有元素的最近公共祖先即为所有 \(\operatorname{lca}(a_i, a_{i+1})\)\(1\leq i < n\))中深度最小的节点。

可以用上面那个性质直接变成区间 RMQ。

修改

链修改

树上差分

若不涉及询问操作或者询问在所有操作之后,可以使用树上差分

子树修改

树上差分

与链修改相同,但差分方向相反。

邻域修改

最神秘的一个

询问

链询问

有些题目会询问 \((u,v)\) 链上某个信息,要注意询问答案可能与链的方向有关。

我们有如下应对方法:

lca拆链

将链拆成两半,\((u, \operatorname{lca}(u, v))\) 以及 \((\operatorname{lca}(u, v), v)\)
这样可以将所有链变成沿父亲方向。使得倍增等方法更容易使用。
注意,如果链有向则还需要处理两种询问:父亲到儿子以及儿子到父亲。

点分治

本质也是拆链的一种方法,将链在最浅的分治点上拆开。

树上莫队

对于区间询问我们可以使用莫队,对于链询问我们可以使用树上莫队。
通过转换成括号序列可以将树上莫队变成序列莫队。

子树询问

邻域询问

计数

动态规划

重链剖分

重链剖分对于 \(dp\) 最重要的性质是:所有轻儿子的 \(siz\) 和为 \(\operatorname{O}(n\log n)\)

由此可以直接暴力合并重儿子与轻儿子。(当合并复杂度为 \(siz\)轻儿子 时)

长链剖分

长链剖分类似,所有轻儿子的 \(h\) 和为 \(\operatorname{O}(n)\)
当合并复杂度为 \(h\) 轻儿子 时可以直接暴力合并。

\(f_{u,k}\)\(u\) 子树内到 \(u\) 距离为 \(k\) 的节点个数。
对每个点 \(u\) 求出 \(\max_kf_{u, k}\)

直接维护 \(f_{u, k}\)\(\operatorname O(n^2)\) 的。
将两个节点 \(v_1,v_2\) 的信息合并,复杂度是 \(O(\min(h_{v_1},h_{v_2}))\),所以长链剖分 \(O(n)\)

点分治

树上链计数
给定一个 \(n\) 个节点的树,对于 \(k = [1, n]\cap \mathbb Z\),求长度为 \(k\) 的链的个数。

将每条链在最浅的分治点上统计。即每个分治点只统计不跨过之前分治点的链。

神秘

树上背包
给定一颗 \(n\) 点树,节点有权值。
对于 \(k = [1, n]\cap\mathbb Z\) 输出:在树上选出 \(k\) 个点形成连通块,其最大的权值和。

\(f_{u,i}\)\(u\) 子树内以 \(u\) 为根,选择 \(i\) 个点形成连通块的最大权值和。
\(f_{u,i}\) 可以通过合并子树信息得到。直接合并看似时间复杂度 \(\operatorname{O}(n^3)\)
实则是 \(\operatorname{O}(\sum_{v1,v2\in son_u} siz_{v_1}siz_{v_2})=\operatorname{O}(n^2)\)
可以看成任意 \((u, v)\) 两点在 \(\operatorname{lca}(u,v)\) 处有一贡献。

posted on 2025-01-08 15:03  Evan_song  阅读(54)  评论(0)    收藏  举报