《算法竞赛》04 高级数据结构

并查集

  • 普通并查集的应用只需要注意边带权的转化。
  • 合并方式优化按深度大小要更合理一些。
  • 可持久化并查集:把原来的 \(fa\) 数组变成可持久化数组,注意不要路径压缩,直接按秩合并即可。
  • 可撤销并查集:还是不要路径压缩,只用按秩合并,每次记录一下修改了什么,撤销的时候倒回去就行。只需要撤销的时候就不需要用可持久化了。
  • 并查集的本质是树(森林),所以有时候可以考虑转化为树上问题解决。
  • Examples

树状数组

  • 树状数组的基础题直接套板子就行了,但是更复杂的应用需要回到树状数组的本质(编号方式)去思考,比如解决不满足线性性的最值问题。
  • 解决像区间修改区间查询这类问题需要一些转化,变为若干可以用树状数组解决的问题。
  • 树状数组可以和倍增思想结合,进行树状数组上二分

线段树

  • 普通线段树不要为了省空间开两倍,不然怎么死的都不知道。开四倍是因为极端情况会刚好多出一个点再最后一行。
  • 考点经常在懒标记和信息维护上,线段树只是糖衣。
  • 吉司机线段树:记录区间的最大值和次大值,两者都要修改时直接递归到儿子。解决区间最值区间修改一类的问题。
  • 线段树非常适合做区间合并,可以合并左右儿子区间对称的信息,也可以合并单向的信息(比如从左往右的)。
  • 找思路别忘了扫描线
  • 如果一道题要用到树套树,那最好还是再想想有没有忽略一些别的信息或者思考一下别的方法,真正要用到树套树的题挺少的(部分分除外)。
  • 线段树上二分比普通二分+线段树查询少一个 \(\log\)

可持久化线段树

  • 经常在把一段区间截出来后搭配线段树上二分
  • 区间修改的主席树其实没有多多少复杂度。可以这样思考:线段树的区间操作是 \(O(\log n)\) 的,主席树的区间修改时的遍历方式其实和线段树的区间操作类似,也就是 \(O(\log n)\) 的,增加的点数也是 \(O(\log n)\) 的,和单点修改没啥差别,只是多了一个标记永久化。

分块与莫队算法

  • 优美的暴力。
  • 考虑一种基于分块思想优化:尽量将每种操作的复杂度平均。例如最简单的, \(O(1)\) 修改,\(O(n)\) 询问,可以尝试变成 \(O(\sqrt n)\) 修改 \(O(\sqrt n)\) 询问。还有不太容易想到的,\(O(\log n)\)\(O(\sqrt n \log n)\) 平均成 \(O(\sqrt n)\) 等。常用值域分块实现。
  • 大分块的考点常在信息维护上。
  • 最经常出现的不是序列分块,而是根号分治。
  • 莫队解决的时序列上的问题,如果是树上问题可以尝试用 DFS 序欧拉序括号序等转化。

块状链表

  • 可以定一组上下界,将每一块的长度维持在中间。
  • 整体思路比较简单,写法也比较多,但是代码似乎巨长,如果要用可以权衡一下,时间不够就用 STL rope。

简单树上问题

  • 树的重心常在点分治点分树中用到
  • 树的直径用两次 DFS 求是最基础的,但是如果加上一些修改就需要从树形 dp 的方向思考了。

LCA

  • 倍增求 LCA 有时会顺带倍增维护一些其他的信息比如求链上最值之类的。
  • tarjan 求 LCA:离线求 LCA,遍历树,到点 \(u\) 时如果 \(v\) 被遍历过,那么 \(lca(u,v)\) 就是 \(v\) 祖先中深度最大的没有被回溯的点。
  • 没有办法离线询问有比较多的情况可以用欧拉序 + RMQ 求 LCA。
  • 理论上来说树剖求 LCA 会比倍增快一点,虽然时间复杂度时一样的。

树上的分治

  • 边分治:将一条边的两边分开解决。需要通过建虚点将树重建成链的样子。不常用。
  • 静态点分治:每次取重心分治,每次都至少会使树的大小减半。
  • 动态点分治(点分树):将点分治每次取的重心建成树,用于带修改的时候。

树链剖分

  • 适用于点权,所以有的题目需要边权转点权。
  • 注意树剖本身就带了一个 \(\log\),如果再结合线段树之类的就是 \(\log^2\),计算时间复杂度的时候不要忽略了。

二叉查找树

  • 基于中序遍历。
  • 形态不唯一,平衡树的思想就是基于这个。

替罪羊树

  • 不平衡率(\(\alpha=max(size_l+size_r)/size\))一般取 \(7\)\(7.5\)
  • 删除的点占比大于 \(\alpha\) 了也要重构。
  • 每次重构找到深度最小的需要重构的点。

Treap 树

  • Treap 的键值满足 BST,优先级满足堆,形态唯一。
  • 旋转 Treap 基本不用了,FHQ 足矣。

FHQ Treap 树

  • 普通的 FHQ Treap 非常简单,只需要注意 pushup 和 pushdown 的位置。
  • 可持久化:split 和 merge 都只会改变一条链,所以将链的信息复制下来改就行了。

笛卡儿树

  • 叫“笛卡尔树”吧。
  • 是一种特殊的 Treap,可以用单调栈 \(O(n)\) 构造。
  • 笛卡尔树 + LCA 可以解决 RMQ 问题。
  • 笛卡尔树一般不会单独考,关于笛卡尔树的题好多都只用到了它的部分性质用来比如计数之类,可以出的很难。

Splay 树

  • 时间复杂度有点玄,反正哪里有改动就 splay 一下。
  • 取一段区间 \(l\)\(r\):将 \(l-1\) 转到根,将 \(r+1\) 转到 \(l-1\) 儿子,此时 \(r+1\) 的左子树就是 \(l\)\(r\) 的区间。

K-D 树

  • 本质是对 K 维平面的分治。
  • 节点的插入和删除使用替罪羊树的思想。
  • 有时候会拿它来优化建图,特别是二维平面上的题。

动态树与 LCT

  • LCT 的实链用以深度为键值的平衡树(一般是 Splay)维护。
  • 如果要维护子树信息可以对 Splay 中每个节点多维护一个所有虚边连向的儿子的信息。
  • 板子比较复杂,尽量背,用的时候现推可能会有很多 BUG。
posted @ 2024-01-08 14:07  牛肉爱吃dks  阅读(9)  评论(0编辑  收藏  举报