hhwdd:这些不都是基础练习吗?
记录一些 hhwdd 讲过的或自学的知识点。听不懂就会口胡 😃
记录的可能会很简单
莫队
考虑对原序列分块。设块长为 \(B\)。按照左端点递增为第一关键字,右端点所在块编号递增为第二关键字对询问排序。左端点递增,左指针总共移动 \(n\);右端点所在块编号递增,所以每次至多移动 \(O(\frac{n}{B})\),总复杂度 \(O(n+\frac{n^2f(n)}{B})\),取 \(B=\sqrt n\) 可达 \(O(nf(n)\sqrt n)\),其中 \(f\) 是加数 / 删数的复杂度。
考虑当删除的 \(f\) 极大时怎么做。可以回滚莫队。右指针向右扩展后,先存一下现在的答案,再扩展左指针;扩展左指针计算答案后,如果是普通莫队需要删除删回上一个左指针出现的位置,但是有删除复杂度就升天了。但是我们已经在左指针扩展之前把答案算了,所以直接改成那个存的状态即可。复杂度 \(O(m\sqrt n)\) 。
线性基
最小的一个基向量 \(b\) 使得这个基向量可以通过异或里面的数求出原序列所以得子集异或和。\(b_i\) 表示线性基的第 \(i\) 位,需要满足的性质是这一位存的数二进制下第 \(i\) 位为 \(1\)。由线性基性质可得线性基里的任意数异或都不能表示线性基里的另外一个数,于是我们考虑高斯消元,对二进制位消元(相当于异或)。插入单个数 \(O(\log V)\)
K-D Tree
解决 \(k\) 维空间下的点信息。建树时轮流遍历 \(k\) 维,每次取当前选定维度的中位数划分左右子树。设 \(n\) 为总点数,则不难证明树高为 \(O(\log n)\)。然后考虑维护当前子树(相当于一个高位矩形)中的 \(k\) 维每一维的最大值,查询高位矩形时直接按照它的性质去遍历。经过一些及其困难的推导可以真证明建树时间复杂度 \(O(n\log n)\),单次查询复杂度 \(O(n^{1-\frac{1}{k}})\)。
考虑如何处理动态插入一个点。我们考虑把当前的点数总数拆分成二进制位,每一位上单独开一个 K-D Tree。这样插入一个点后总点数加一,对收到影响的 K-D Tree 进行重构即可。经过一些及其困难的推导可以证明在 \(k=2\) 下这么做的时间复杂度是 \(O(n\log^2 n)\)。
由此可见,K-D Tree 最难的部分是复杂度分析。
cdq 分治
例题是三维偏序。首先对一维排序,然后消掉一维。考虑分治函数 \(cdq(l,r)\)。令 \(mid=\lfloor\frac{l+r}{2}\rfloor\),先求解子问题 \(cdq(l,mid)\),再求解子问题 \(cdq(mid+1,r)\)。此时我们考虑所以满足 \(i\in[l,mid],j\in [mid+1,r]\) 的点对 \((i,j)\)。发现由于原序列排过序且 \(i<j\) 已经消掉了一维。于是运用双指针和树状数组去做二维偏序即可。由主定理得时间复杂度为 \(O(n\log^2 n)\)。
cdq 分治优化 1D dp 的时候就先去左子树分治,再处理转移,再去右子树分治,这样 DP 的转移顺序就是对的。
整体二分
更简单。
\(q\) 次询问,若每次询问都能用二分答案 \(O(n\log n)\) 解决,则总时间复杂度是 \(O(qn\log n)\)。
发现重复调用了很多次 \(check(mid)\),这对于每次调用 \(O(n)\) 的开销是及其浪费的。所以我们考虑把询问离线下来,直接对所有询问一起二分,这样 \(check\) 只会调用一次。更新 \(check\) 的时候为了保证复杂度正确需要运用一些数据结构。\(n,q\) 同阶,复杂度疑似 \(O(n\log^2 n)\)。
kruskal 重构树
其实是一道题 hhwdd 说的“贪心”做法,本质也是贪心。
考虑查询带边权树上两点 \(u,v\) 所经过路径的最小 / 最大权值。kruskal 重构树是比树上倍增 / 树剖常数小,好写的做法。对边权排序,然后跑 MST。按秩或树高合并,每次联通的边权就是这两个联通块内部的点互相跨越的答案。然后发现 \(u,v\) 的答案就是并查集树上的 \(lca\)。然后发现并查集树高只有 \(O(\log n)\) 所以直接暴力就可以做到 \(O(\log n)\)。

浙公网安备 33010602011771号