数据结构入门级技巧
实时更新,遇到好玩的数据结构trick都会放到这里
\(1.\) 线段树合并对于子树查一类问题的应用:
由于子树查问题的询问之间往往具有信息包含关系,所以我们可以考虑将询问离线下来,通过一次 \(dfs\) 将所有子树的信息全部求出来,并在途中回答询问。由于线段树合并过程中线段树代表的就是整个子树内的内容,所以类似于子树颜色众数之类的问题也是可以解决的。
\(2.\) 区间某数出现次数的实现方式:
虽然主席树可以做到在线 \(O(n \log_2 n) - O(\log_2 n)\),空间复杂度 \(O(n \log_2 n)\),但是查询效率、预处理时间、空间没有一项特别拔尖。我们可以考虑用 \(vector\) 记录每一个数的所有出现位置,然后查询时直接在对应的 \(vector\) 上二分即可,在线 \(O(n) - O(\log_2 n)\) 且空间复杂度 \(O(n)\)。考虑如何更快地回答查询:我们用分块预处理出 \(cnt_{i, j}\),前 \(i\) 块中 \(j\) 的出现次数。这样对于整块,我们可以直接 \(O(1)\) 查询,而对于散块内部的个数,我们再次在 \(vector\) 上二分求出个数,这个算法在线,预处理 \(O(n \sqrt{n})\),空间 \(O(n \sqrt{n})\),但是时间变成了 \(O(\log_2{\sqrt{n}})\),近似于大常数 \(O(1)\)。考虑将线段树改为 \(\sqrt{n}\) 叉的,也就是分块。此时类似于主席树一样,将每次的修改都记录下来,可以做到 \(O(\sqrt{n})\) 插入 \(+\) 可持久化,\(O(1)\) 查询,在线 \(O(n \sqrt{n}) - O(1)\),空间复杂度为 \(O(n \sqrt{n})\)。
\(3.\) 分块对于修改简单,查询复杂题目的应用:
如果修改只是单点修改,但是查询比较非常规,可以考虑对于每一块维护较为复杂的信息,然后单点修改时直接 \(O(\sqrt{n})\) 暴力重构块。
\(4.\) 位运算:
维护位运算信息时,如果固定左端点,总共的 \(and, or\) 等信息的可能种数只有 \(\log_2 V\) 种(\(V\) 代表值域)。此时我们就可以暴力二分出所有不同值的区间,进行信息的维护。
\(update:\) 其实 \(gcd\) 也只有 \(\log_2 V\) 种取值,因为 \(gcd\) 每次变小一定是除以一个至少为 \(2\) 的正整数。
\(5.\) \(st\) 表的应用:
所有满足可以 \(pushup\),两个相同信息作用后不变的信息都是可以用 \(st\) 表维护的。例如区间 \(gcd\),区间按位与,区间按位或,区间 \(lca\) 等。
\(6.\) 第 \(k\) 小值:
第 \(k\) 小值可以通过一个 \(\log_2 V\) 的代价二分答案而转化为区间小于 \(x\) 的值的个数,便于维护。
\(7.\) 字符串相关题目:
遇到字符串上区间修改区间查询等问题时,可以考虑建字符集个线段树来处理问题,一般会将问题简化很多。而正解绝大多数情况下都是要依赖字符集大小的,不然这道题可以直接搬到序列上。
\(8.\) 线段树合并的应用:
线段树合并可以在过程中每一次合并后新开一个节点,这样即使是强制在线也可以直接在查询时在对应位置查询,空间消耗也不是特别大。
\(9.\) 树上问题:
当树上问题只涉及对 \(x\) 子树查询的时候,换根是假的,因为新的根如果是 \(x\) 的祖先或者其祖先的其他子树内的点,那么显然子树不变,如果是 \(x\) 那么显然子树是整棵树,如果在 \(x\) 的子树中那么子树就是整棵树刨掉一部分子树,对应在 \(dfs\) 序上就是两段区间。