OI+ACM 笔记:D - 数据结构

 


一些技巧与思想也会归类于数据结构。

D - 数据结构 序列结构

树状数组

lowbit(x) 函数:表示 x 的二进制表示中,最低位的 1 的数值大小,lowbit(x) = x & -x

ci 的管辖区间[ilowbit(i)+1,i]

O(n) 建树

  • 方法一:每一个节点的值是由所有与自己直接相连的儿子的值求和得到的,因此可以倒着考虑贡献。每次确定完 ci 的值后,用 ci 的值更新 ci+lowbit(i)
  • 方法二:预处理前缀和数组,则 ci=preipreilowbiti

O(logn) 查询第 k:树状数组维护每个数的出现次数,倍增求解。

线段树

  • 任意区间 [l,r] 在线段树上,至少需要 O(logn) 个节点覆盖。

动态开点线段树 & 线段树合并 & 线段树分裂

  • 动态开点线段树,单次插入新建节点个数 O(logA)。其中 A 表示值域大小。
  • 动态开点线段树可以使用懒标记,下传标记时需要新建结点(若标记对空节点无影响则不必新建节点)。
  • 遇到仅针对某个权值的修改和询问时,可以考虑对每个权值开一个动态开点线段树。
  • 线段树合并单次复杂度,取决于两棵线段树的交集大小;线段树合并总复杂度,取决于所有线段树的节点总数。
  • 线段树合并支持持久化,需要在合并时新建节点。
  • 在线段树合并的过程中,可以统计来自不同线段树且具有相对顺序关系的信息。例如逆序对。
  • 在线段树合并的过程中,在处理复杂的问题时,可以尝试分成以下几类讨论(其中 2, 3 是讨论重点):
    1. p,q 均为空节点:返回空节点。
    2. p,q 中分别有一个空节点和一个非空节点:特殊讨论。
    3. 线段树递归到叶子节点(即 l=r):特殊讨论。
    4. p,q 均为非空节点:分别合并 p,q 的左儿子与 p,q 的右儿子,最后 update。
  • 线段树分裂单次复杂度 O(logA),新建节点个数 O(logA)

主席树

  • 主席树可以作为线段树的前缀和来使用。
  • 主席树区间修改,可以标记永久化。
  • 主席树可以将其他版本树的某段区间,嫁接到当前版本树的该段区间上。但要注意将公共部分的标记永久化下放。

势能线段树

  • 势能线段树,用于维护修改操作使得被修改元素的势能迅速向零势能逼近的信息。
  • 在区间修改时,暴力往叶子节点递归。若递归途中遇到某个节点,该节点的对应区间内每个元素都不能进行有效修改,则在该节点 return
  • 在无修改操作时,时间复杂度为 O(nαlogn)。其中 α 为有效修改次数上限。

区间开平方:一个正整数 x 被开方 O(loglogx) 次后会变成 1。若区间最大值 =1,则 return

区间取模:一个整数 xp 取模,若 xp,则 x 至少变小一半。若区间最大值 <p,则 return

区间除法、区间加:整除一个数 p,会使区间最大值与最小值的差至少变小一半。

  • maxmin=0,则打加法标记。
  • maxmin=1maxpminp,则打加法标记。

区间按位与:一个二进制数 xv 进行按位与,相当于是把所有 v0 的位,在 x 中强制置为 0。若 v0 的位置集合被区间按位或和0 的位置集合包含,则 return

区间取最值(吉司机线段树):以区间取最小值为例。

  • 在线段树上的每一个节点 p,需要维护:
    • max:区间最大值。
    • cnt:区间最大值的个数。
    • smax:区间严格次大值。
  • 在区间修改时,若递归途中遇到一个被询问区间完全包含的节点 p 时:
    • xmaxp,则 return
    • xsmaxp,则暴力往左右儿子递归。
    • smaxp<x<maxp,则有 cntmaxp 被改为 x。打标记即可。
  • 无区间加时,时间复杂度 O(nlogn)
  • 有区间加时,时间复杂度 O(nlog2n)

历史线段树

  • 待填。

李超线段树

  • 李超线段树,用于维护一个二维平面,支持插入一个定义域为 [l,r] 的一次函数 kx+b,查询某个横坐标上最大的函数值。
  • 李超线段树上的每一个节点,需要维护区间的优势线段(在 mid 处取到最大函数值的线段),用标记永久化的方法处理。
  • 李超线段树支持持久化。
  • 李超线段树合并总复杂度,取决于所有线段树的节点总数。
  • 李超线段树合并支持持久化,需要在合并时新建节点。
  • 李超线段树仅支持插入函数,不支持删除函数。但可以考虑线段树分治。

楼房重建式线段树

参考:https://www.cnblogs.com/PinkRabbit/p/Segment-Tree-and-Prefix-Maximums.html

写法 1:当需要维护的信息满足可减性时,可以考虑采用写法 1。

  • 在线段树上的每一个节点 p,需要维护:

    • max[p]:区间最大值。
    • dat[p]:区间内所有前缀最大值的信息和。
  • 定义函数 calc(p,pre) 表示以 pre 为初始的前缀最大值时,区间内所有前缀最大值的信息和。

    • p 为叶节点,则 calc(p,pre)=[pre<max[p]]
    • pre<max[lc[p]],此时考虑完左子树后前缀最大值必定为 max[lc[p]],而右子树的信息和可以通过做差得到。

    calc(p,pre)=calc(lc[p],pre)+(dat[p] deduct dat[rc[p]])

    • premax[lc[p]],此时左子树不会出现前缀最大值,只需考虑右子树即可。

    calc(p,pre)=calc(rc[p],pre)

  • update 时,有 dat[p]=dat[lc[p]]+calc(rc[p],max[lc[p]])

写法 2:当需要维护的信息不满足可减性时,可以考虑采用写法 2。

  • 在线段树上的每一个节点 p,需要维护:

    • max[p]:区间最大值。
    • dat[p]:以左子树的区间最大值为初始的前缀最大值时,右子树内所有前缀最大值的信息和。特别地,叶子无定义。
  • 定义函数 calc(p,pre) 表示以 pre 为初始的前缀最大值时,区间内所有前缀最大值的信息和。

    • p 为叶节点,则 calc(p,pre)=[pre<max[p]]
    • pre<max[lc[p]],同上

    calc(p,pre)=calc(lc[p],pre)+dat[p]

    • premax[lc[p]],同上

    calc(p,pre)=calc(rc[p],pre)

  • update 时,有 dat[p]=calc(rc[p],max[lc[p]])

猫树

  • 建猫树时,采用堆式建树(节点 p 的左、右儿子分别为 2p2p+1)。具体实现时不必显式地将左右儿子的结构建出来,只需记录每一层深度的具体信息,与每个元区间 [l,l] 对应的叶子节点编号即可。
  • 若将序列长度补成 2 的整数次幂:不难发现左儿子编号为父节点编号左移一位,右儿子编号为父节点编号左移一位再加一。
    • [l,l][r,r] 的代表节点 x,y 的 LCA 即为 x,y 二进制下从高位向低位的 LCP。
    • [l,l][r,r] 的代表节点为 x,y,则 LCA 编号为 x >> logx[x ^ y],深度为 logx[x] - logx[x ^ y]。特别要注意的是 logx[0] = 0

平衡树

splay

  • splay 核心思想:每次操作完后,通过双旋 & 单旋将相关节点 x 旋转至根,以此维持树高稳定。
  • splay 的三种旋转:
    • x 的父亲 p 是根节点时,直接 zig/zag x
    • x 和上两代祖先位于一条链上:先 zig/zag p,再 zig/zag x
    • x 和上两代祖先分叉时:先 zig/zag x,再 zig/zag x
  • splay 基于均摊分析,不支持持久化。
  • splay 启发式合并的时间复杂度 O(nlogn)(splay 是 Dynamic Finger Search Tree,需要保证第二棵树按中序遍历的顺序插入)。

FHQ treap

  • treap 的每个节点有两个关键字 valdat,其中数值 val 满足 BST 性质,随机键值 key 满足 heap 性质。故 treap 为笛卡尔树。
  • treap 的随机性,可以保证期望树高为 O(logn),分析同快速排序。
  • 当 FHQ treap 用作区间树时,可以用笛卡尔树的建树方式 O(n) 建树。
  • FHQ treap 的合并操作,不能合并值域有交集的两棵树。
  • FHQ treap 支持持久化,需要在分裂时复制节点。涉及到打标记时,需要在标记下传时复制节点。在合并时要用 rand() % (t[p].sze + t[q].sze) < t[p].sze 来代替随机键值大小的判断。
  • FHQ treap 可以用持久化的方式复制区间。

树套树

树状数组套线段树

  • 常用于动态维护线段树的前缀和。

线段树套线段树

  • 常用于动态维护一个 n×m 的矩形。涉及到区间修改时,需要标记永久化。

线段树套平衡树

  • 常用于动态维护平衡树的区间和。

笛卡尔树

  • 每个节点由一个二元组 (key,val) 组成,其中键值 key 满足 BST 性质,权值 val 满足 heap 性质。
  • 若每个节点的 key,val 均互不相同,则笛卡尔树的形态是唯一的。

建树:以大根堆为例,将所有元素按 key 从小到大排序。每次插入的元素必然在该笛卡尔树的右链上(BST 性质),在右链查找第一个大于等于当前值的节点,将链拆成两部分,上半部分的下端接在当前节点的父亲上,下半部分的上端接在当前节点的左儿子上。

  • 对序列 a 建笛卡尔树(小根堆),则 vu 的祖先当且仅当 avu,v 之间的最小值。

分块

  • Bn 为分块大小,t 为分块数量,Lp[], Rp[] 为当前块的左右端点,belong[] 为当前点所属块的编号。
  • 当遇到单点修改、区间查询时,可以考虑用分块优化到 O(1) 修改 O(n) 查询。
  • 当遇到区间修改、单点查询时,可以考虑用分块优化到 O(n) 修改 O(1) 查询。
  • 当遇到区间修改、区间查询时,可以考虑用差分转化为单点修改、区间查询。
  • 当遇到限定 ii+ki,询问从 x 出发首次 y 的行动步数时,可以预处理每个元素走出所在块后到达的点及所需步数。
  • 当遇到区间 [l,r] 的子区间 [l,r](llrr) 统计问题时,可以尝试分成以下几类讨论:
    • l,r 均在整块内。
    • l,r 中分别有一个在整块内、一个在散块内。
    • l 在左散块,r 在右散块。
    • l,r 均在左散块或 l,r 均在右散块。
  • 特别要注意的是,在使用 sqrt() 前要调用 #include <cmath>

莫队

普通莫队

  • 最优排序方式等价于平面曼哈顿距离最短哈密顿路径,这是 NP 完全问题。
  • 莫队排序方式:
    • 第一关键字升序:左端点 l 所在块编号。
    • 第二关键字升序:右端点 r
  • 奇偶性优化:当 belong[l] 为奇数时,第二关键字升序;当 belong[l] 为偶数时,第二关键字降序。
  • 当左右端点扩展时,为避免出现 l>r 的情况,尽量保证区间先扩大,后缩小。
  • n,m 同阶时,分块大小 O(n),时间复杂度 O(nn)
  • n,m 不同阶时,分块大小 O(nm12),时间复杂度 O(nm12)

高维莫队

  • 对于 k 维莫队,对前 k1 维分块,第 i[1,k1] 关键字为第 i 维坐标所在块编号,第 k 关键字为第 k 维坐标。
  • 分块大小 O(nm1k),时间复杂度 O(nm11k)

带修莫队

  • 普通莫队是不支持修改的。
  • 可以给每个询问附加一个时间戳 t,来表示回答该次询问时已经经过了多少次修改。转化为三维莫队。
  • n,m 同阶时,分块大小 O(n23),时间复杂度 O(n53)
  • n,m 不同阶时,分块大小 O(nm13),时间复杂度 O(nm23)

回滚莫队

  • 用于解决一类插入容易、删除不易的莫队问题。
  • 当操作方便撤销时,可以考虑直接撤销。或是用栈记录操作,沿着栈撤销。
  • 当操作不方便撤销时,可以考虑加入这些数对答案的影响。

树上莫队

欧拉序 2:在 dfs 时,第一次访问到该节点时将该节点加入欧拉序(记作 Inx),从该节点回溯时将该节点加入欧拉序(记作 Outx)。

  • 对于从 xy 的路径,不妨设 Inx<Iny
    • LCA(x,y)=x,则从 xy 的路径上的点为欧拉序 [Inx,Iny] 中只出现过一次的点。
    • LCA(x,y)x,则从 xy 的路径上的点为欧拉序 [Outx,Iny] 中只出现过一次的点与 LCA(x,y)

莫队二次离线

  • 待填。

函数函数函

序列分治

  • 在序列分治的过程中,取分治重心 mid=l+r2​,尝试分成以下几类讨论(其中 3 是讨论重点):
    1. 分治递归到叶子节点(即 l=r)。
    2. 左右端点均在区间 [l,mid] 或左右端点均在区间 [mid+1,r]
    3. 左端点在区间 [l,mid],右端点在区间 [mid+1,r]
  • 序列分治的分治树为猫树。

最值分治

  • 在最值分治的过程中,取分治重心 p[l,r]​ 中的最值,尝试分成以下几类讨论(其中 3 是讨论重点):
    1. 分治递归到叶子节点(即 l=r)。
    2. 左右端点均在区间 [l,p1] 或左右端点均在区间 [p+1,r]
    3. 左端点在区间 [l,p],右端点在区间 [p,r]。此时的子区间 [l,r] 中的最值必为 ap。通常需要配合启发式合并。
  • 最值分治的分治树为笛卡尔树。

CDQ 分治

  • 在序列分治的过程中,取分治重心 mid=l+r2​,尝试分成以下几类讨论(其中 3 是讨论重点):
    1. 分治递归到叶子节点(即 l=r)。
    2. 递归计算区间 [l,mid] 中的答案。
    3. 统计区间 [l,mid] 对区间 [mid+1,r] 的贡献。
    4. 递归计算区间 [mid+1,r] 中的答案。

整体二分

  • 在整体二分的过程中,取二分中点 mid=l+r2,将答案 mid 的询问分到区间 [l,mid] 中递归计算,将答案 >mid 的询问分到区间 [mid+1,r] 中递归计算。

二进制分组

  • 枚举每一个二进制位 i。在此次分组中,若一个关键点的第 i 位为 1,则将其归入 A 类点;若一个关键点的第 i 位为 0,则将其归入 B 类点。计算 A 类点与 B 类点之间的信息。
  • 二进制分组一定覆盖了所有的关键点点对,因为对于两个不同的关键点,必有一个二进制位不相同。

线段树分治

  • 待填。

线段树优化建图

  • 待填。

Trick

区间加、区间查询 转 单点加、区间查询

  • 对于原序列 a,记其差分序列为 b

i=1rai=i=1rj=1ibj=j=1rbj(rj+1)=(r+1)j=1rbjj=1rjbj

连续段计数

无重复元素:即统计 maxmin=rl 的区间个数。

  • 注意到 maxminrl,即 maxmin+lr
  • 从左到右枚举 r,使用线段树配合两个单调栈,维护每个 l 对应的 maxmin+l,线段树上需要维护区间中的最小值以及最小值个数
  • 若线段树 [1,r] 中的最小值等于 r,则 r 对答案有 " 线段树 [1,r]​ 中的最小值个数 " 的贡献。

有重复元素:即统计 maxmin+cnt=rl 的区间个数,其中 cnt 表示区间中重复数的个数。

  • 注意到 maxmin+cntrl,即 maxmin+cnt+lr
  • 从左到右枚举 r,使用线段树配合两个单调栈以及每个位置上一个相同数的位置,维护每个 l 对应的 maxmin+cnt+l,线段树上需要维护区间中的最小值以及最小值个数
  • 若线段树 [1,r] 中的最小值等于 r,则 r 对答案有 " 线段树 [1,r] 中的最小值个数 " 的贡献。

D - 数据结构 树上结构

最近公共祖先 LCA

树上倍增

  • O(nlogn)O(logn)

重链剖分

  • O(n)O(logn)

欧拉序 & ST 表

  • O(nlogn)O(1)

欧拉序 1:在 dfs 时,第一次访问到该节点时将该节点加入欧拉序(记作 Firx),回溯到该节点时将该节点加入欧拉序。

  • 不妨设 Firx<Firy,则 LCA(u,v) 为欧拉序 [Firx,Firy] 中深度最小的节点。用 ST 表维护。

树的重心

  • 一个点是树的重心,当且仅当它的最大子树大小不超过整棵树的一半
  • 树中所有点到重心的距离和是最小的。
  • 树至多有两个重心。若树有两个重心,则这两个重心相邻,此时树有偶数个节点,可以被划分成大小相等的两支。
  • 在两棵树之间加一条边,新树的重心在原来两棵树的重心的简单路径上。
  • 在一颗树上添加或删除一个叶子,重心至多只移动一条边的距离。

树的直径

  • 在边权非负的前提下,合并两个点集时,新点集的直径端点,必定来自两个点集四个端点中的某两个,共六种情况。
  • 在边权非负的前提下,钦定点对 (u,v) 作为直径端点的充要条件是,不存在其他点 w 使得 dist(u,w)>dist(u,v)dist(v,w)>dist(u,v)

树的同构

T1,T2 同构:若 T1,T2 为同构树,当且仅当存在一个 1n 的排列 f,使得 (i,j)E(fi,fj)E

有根树自同构树计数:对于每个点的每个儿子的子树,按照同构(指去掉标号、儿子间不区分顺序后俩子树相同)划分出等价类,按照每种等价类的大小的阶乘乘入答案。

无根数自同构数计数:需要找到一个代表节点,当重心唯一时,取重心为根;当重心不唯一时,在重心之间加一点,取该点为根。转化为有根树计算。

树哈希

有根树树哈希:设 fu 表示以 u 为根的子树的哈希值,则 fu 即为 u所有儿子 v 为根的子树的哈希值构成的多重集的哈希值。考虑使用集合哈希。

fu=(c+vson(u)shift(fv))modm

无根树树哈希:需要找到一个代表节点,当重心唯一时,取重心为根;当重心不唯一时,在重心之间加一点,取该点为根。转化为有根树的情况。

重链剖分

  • 重链优先 dfs 序列:
    • 任意子树在 dfs 序上,都对应一个连续区间。
    • 任意重链在 dfs 序上,都对应一个连续区间。
  • 向根的方向经过一条轻边后,所在子树大小至少乘 2
  • 任意一点到根路径上的所有边,可分成 O(logn) 个重路径的不交并。
  • 任意两点之间路径上的所有边,可分成 O(logn) 个重路径的不交并。

长链剖分

  • 所有长链的长度和为 n
  • 任意一点到根路径上的所有边,可分成 O(n) 个长路径的不交并。

长链剖分优化 dp:长链剖分可以优化附带深度维的 dp。

  • 遍历每一个节点,统计每一个节点子树内的信息,设当前遍历到了节点 u
    • 递归计算 u 的长子树的信息,将长子树的 dp 信息继承给 u
    • 递归计算 u 的所有短子树的信息,将短子树的 dp 信息合并计入 u
  • 具体实现中,可以开一个内存池 pool,为一条长链统筹分配内存,通常链上不同的节点有不同的首指针,便于继承信息。因此,在完整地做完一遍 dp 后,只有长链链首的节点保留了正确的 dp 值。

O(nlogn)O(1) 树上 k 级祖先

  • 任意一点 xk 级祖先,其所在的长链长度 k
  • 对树进行长链剖分,预处理:
    • 每个点的树上 2k 级祖先。
    • 每条长链(记该长链长度为 len)从长链顶端向上的 len 个祖先和向下的 len 个儿子。
    • 每个二进制数的最高位。
  • 每次询问 xk 级祖先:
    • hk 的最高二进制位。利用倍增数组先让 x 跳到 x2h 级祖先。
    • 记剩余级数为 k=k2h。由于当前 x 所在长链长度 len2hk2h=k,可以先让 x 跳到 x 所在长链顶端,若剩余级数为正,则利用预处理的数组向上跳求出答案;若剩余级数为负,则利用预处理的数组向下跳求出答案。

LCT

  • LCT 的辅助树由 splay 森林组成,每一棵 splay 都维护了一条偏爱链,splay 的中序遍历对应着偏爱链从链顶至链底。
  • LCT 的每一棵 splay 的顶端节点,其父不认子。
  • LCT 的操作注意事项:
    • find_root(x):在找到根之后,需要将根 splay 上去,否则复杂度可能会退化。
    • link(x, y):在连边之前,需要判断 x,y 所在连通块的根是否不同。
    • cut(x, y):在执行完 make_root(x), access(y), splay(y) 之后,需要判断 y 的左子树是否有且仅有 x
  • LCT 经过 access 后,跨越非偏爱边的数量是 O(logn) 的。
  • LCT 的 access 操作,可以实现从某节点到根路径上的标记覆盖,可以实现从某节点到根路径上标记的批量处理(因为每条实链的标记都是相同的)。由于根是不变的,就不必写翻转标记了。

LCT 维护子树信息:维护虚子树的信息总和(需要满足可减性),只有 accesslink 操作需要改变。

LCT 维护生成树

  • 对于带权边 (u,v,w),新建权值为 w 的点 p,连接边 (u,p)(p,v),转化成点带权的树。
  • 加入带权边 (u,v,w) 时(以最小生成树为例),记生成树 uv 的路径上的最大边为 w。若 w<w,则使用新边替换最大边;否则无法替换。使用 LCT 实现 linkcut

LCT 维护双连通分量待填。

LCT 维护黑白转色树(qtree6):定根,令每个黑点向黑点父亲连边,此时真正的连通块就是 splay 里的连通块删掉根。

点分治、点分树

参考:https://liu-cheng-ao.blog.uoj.ac/blog/2969

  • 点分治本质上是序列分治的树上衍生。
  • 在点分治过程中,将本层中与上一层重心相邻的点记作本层的根。若将以 " 上一层的根 " 作为根时的子树大小作为本层总节点数,虽然在本层的根在上一层的根与上一层的重心简单路径上时,本层总结点数会计算错误,但不影响复杂度。
  • 点分树本质上是点分治的分治树。
  • 点分树中任意两点 u,vLCA(u,v) 一定在原树中 u,v 的简单路径上。

虚树

  • 对于关键点点集 S,在包含关键点点集 S 的极小联通子图中,分叉节点(儿子数 2 的节点)的数量不超过 |S|1
  • 对于关键点点集 S,虚树大小 2|S|1
  • 对于关键点点集 S,记 ui 为点集 S 中的点按 dfs 序排序后的结果,则

{LCA(ui,uj)1i<j|S|}={LCA(ui,ui+1)1i<|S|}

DSU on tree

  • 遍历每一个节点,统计每一个节点子树内的信息,设当前遍历到了节点 u
    • 递归计算 u 的所有轻子树的信息,回溯时需要撤销贡献。
    • 递归计算 u 的重子树的信息,回溯时不需要撤销贡献。
    • 遍历 u 的所有轻子树,统计所有轻子树的贡献。
  • 一个节点在重子树内被统计 1 次,在轻子树内被统计 O(logn) 次(取决于该点到根有多少条轻边)。

Trick

树上倍增

  • 现要求 xy 的信息和,若该信息满足 " 可重复贡献性 ",则可以将 xy 拆成 xLCA(x,y)yLCA(x,y) 两段直链,将每一段直链分别拆成自链底向上、自链顶向下的长度为 2 的极大整数次幂的链,用三次合并计算四个树上倍增数组的信息和即可。

树上差分

  • 对于大小为 m 的关键点点集 S,现在要求包含关键点点集 S 的极小联通子图的边权和,记 ui 为点集 S 中的点按 dfs 序排序后的结果,则答案为

12i=1mdist(ui,u(imodm)+1)

  • 对于大小为 m 的关键点点集 S,现在要给包含关键点点集 S 的极小联通子图的所有点加上 d,考虑树上差分后做一遍子树和,记 ui 为点集 S 中的点按 dfs 序排序后的结果,则

    • 对于 1im,令 ui 的差分数组加上 d
    • 对于 1i<m,令 LCA(ui,ui+1) 的差分数组减去 d。特别地,令 faLCA(u1,um) 的差分数组减去 d
  • 对于大小为 m 的关键点点集 S,现在要给包含关键点点集 S 的极小联通子图的所有边加上 d,考虑树上差分后做一遍子树和,记 ui 为点集 S 中的点按 dfs 序排序后的结果,则

    • 对于 1im,令 ui 的差分数组加上 d
    • 对于 1im,令 LCA(ui,u(imodm)+1) 的差分数组减去 d

路径加、子树查询 转 单点加、区间查询

  • 对于一个路径加 (path(x,y),+d),记 z=LCA(x,y),将其拆成四个到根节点的路径加

(path(1,x),+d)(path(1,y),+d)(path(1,z),d)(path(1,faz),d)

  • 一个到根节点的路径加 (path(1,x),+d),会对 x 的祖先节点 u 产生 ddepxd(depu1) 的贡献。

换根

  • 钦定以 1 为根,考虑换根对信息的影响。
  • 设当前的根为 R
    • xR,则以 R 为根时 x 的子树,为整棵树
    • xR 的祖先,则以 R 为根时 x 的子树,为整棵树刨去以 1 为根时 s 的子树,其中 sxR 方向上的儿子。
    • 若是其他情况,则以 R 为根时 x 的子树,为1 为根时 x 的子树
  • 设当前的根为 R,则以 R 为根时的 LCA(x,y),为以 1 为根时 LCA(x,y),LCA(x,R),LCA(y,R) 中深度最大者。

森林 / 沙漠 连通块计数

  • 森林:连通块个数 = 点数 边数。
  • 沙漠:连通块个数 = 点数 边数 + 环数。
posted @   Calculatelove  阅读(434)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示