树相关算法与问题
算法或数据结构
树
无环连通图
性质:
- 无环
- 联通
- \(|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)\),以及可快速合并。
- 若支持区间修改则需要懒标记,要求修改信息有结合律,且可快速合并。
Link-Cut-Tree
更高级的平衡树(
支持动态连边删边,维护的实际上是森林。
性质:
- 实链剖分的特殊性质:\(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)\) 处有一贡献。
浙公网安备 33010602011771号