网课-基础优化技巧
三分
三分等价于二分斜率。
因此完全可以求导后直接做二分。
01 分数规划
整体二分
对多个个体同时做二分。
具体地,将满足某个条件、不满足某个条件的询问分为两类,进行分治。
因此,是一个离线算法。
考虑对单个星球二分。则基础想法是以时间为线段树下标,在主席树中二分。
考虑拓展到整体二分。则基础想法是二分时间,试着加入陨石雨。可以用树状数组做到 \(O(n \log^2 n)\),也有差分 + 离散化 \(O(n \log n)\)。
分治
主要思想大概是把大问题分成小问题再合并起来。
所以一般要求问题有可分性, 同时有可合并性。
CDQ 分治:排序解决 \(a_i\),归并解决 \(b_i\),树状数组解决 \(c_i\)。
有趣东西:高维偏序的 bitset 做法
CDQ 例题:P4655 [CEOI2017] Building Bridges
CDQ 分治处理点对问题。
见到形如 \(n \times m\) 的形式,我们可以想到:
最小值上界、最大值下界 为根号。
考虑分治。每次分治,考虑对当前矩形的长边进行分治,处理短边。
令 \(w = n \times m\),根据主定理,分治部分有复杂度:\(T(n) = 2T(\frac{n}{2}) + n^{\frac{3}{2}} \log n = O(n^{\frac{3}{2}} \log n)\)。
查询部分复杂度另算。其实我们不一定要保证只在一层就更新到查询的答案。复杂度:\(T(n) = T(\frac{n}{2}) + q\sqrt{n} = O(q\sqrt{n})\)。
对于分治问题,我们一定要注意:
-
要么:每一层的操作复杂度一定为严格关于 \(n\) 的多项式函数。
-
要么:分治一类东西,但操作复杂度也关于 \(n\)。
一个左、中、右三者匹配的问题,也是点对形的 CDQ 分治。
.
(分治从哪里切开是无所谓的。。。)
倍增
看上去像废话一样的步骤:
-
观察到可以倍增(某种单调性)。
-
转化原问题。
哈希
用于判相等。
字符串哈希:选取一个大质数 \(M\) 与一个随机奇数 \(P\),将字符串看作模 \(M\) 意义下的 \(P\) 进制数。比较次数较大时,需要使用双模数(\(10^9+7,10^9+9\))。
哈希的要点:设计合适的哈希函数,使得你想要保留的特征被保留,不希望保留的特征不被保留。
我们不用保留“顺序”这一特征。因此对每个数赋随机权值,做异或哈希即可。
Trie
Trie 可以反映两个字符串之间的前后缀关系。
通过字典树得到字符串之间的前后缀关系,以比较大小关系。再用拓扑排序辅助。
01Trie 经典应用:最大异或数对。
等一会儿,等一会儿。
KMP
border(s):关于字符串 s 的一个字符串集合,满足其中的所有字符串都同时为 s 的前缀和后缀。
next[s]:字符串 s 的最长 border 对应的前缀(的下标)。
去看题解的线性做法。
需要注意的一点是,我们一直都是对模式串做预处理的。
KMP 自动机:
实际是 AC 自动机的弱化形式。设 \(trans[x][c]\) 表示模式串匹配到第 \(x\) 位后,文本串下一个字符为 \(c\),模式串最多匹配到哪一位。查询时按照文本串遍历即可。
以下两道题就是常规的自动机上 DP:
下面这道题要用到可持久化 KMP 自动机(?):
记忆化搜索
路径条数统计问题。
跑最短路,得到最短路长度数组 \(dis[x]\)。有显然 DP 设 \(f[x][w]\) 表示 \(1 \sim x\) 长度为 \(dis[x]+w\) 的路径条数。按照 \(dis[x]+w\) 排序遍历每一个状态;如果遇到 0 边,还需考虑拓扑序的问题。
【如果用记忆化搜索的话,我们就不用考虑“拓扑序”的问题了。这就是它的优势。】
另外还要注意判最短路经过 0 环的情况。只用找出所有 0 环,并建反图判断其上的点是否可能在路径上即可。
meet in the middle
将原有数据分成两部分分别进行搜索,最后在中间合并。
信息需要可单次合并。
一般 \(n\) 的范围在 \(40 \sim 50\)。meet in the middle 可使复杂度开根号。
没有听懂一点。
剪枝
-
可行性剪枝:如果当前分支已经和题目要求的不同那么就直接忽略。
-
最优化剪枝:如果当前分支没有之前已经搜索过的优,则不再往下搜索。
-
冗余性剪枝:当几个枝桠具有完全相同的效果的时候,只选择其中一个走就可以了。
-
顺序剪枝:结合了上面几种剪枝,不同的搜索顺序就可以有天差之别。如果是再特殊构造的数据中,将数据随机打乱可以获得不错的效果。另外将搜索算法与贪心结合,也可能得到很好的效率。
离散化
有时可能还需要保留关键点附近的点。(例:\(x-1, x, x+1\))
扫描线 & 二位数点
将信息抽象为一个二维正方形(二维数点),我们将一维按照偏序关系遍历,过程中记录另一维信息。
还有一种扫描线一维是右端点,一维是左端点。具体来说就是从左往右扫右端点,用 ds 维护每个点作为左端点的答案。
可持久化线段树是扫描线的在线形式。
区间数颜色问题(卡莫队!)。
记录每种颜色第一次出现的位置,即满足可减性,可以做二位数点。
变体:给定一个序列,每次查询区间中出现偶数次的数的异或和。
- 给定一棵 \(n\) 个点的树,有 \(q\) 次查询,每次查询区间 \([l,r]\),表示询问当这个树仅剩点和边的编号在 \([l,r]\) 之间时连通块的个数。\(1 \le n \le 10^6\)。
可以发现答案为点数减去端点均在 \([l,r]\) 内的边数。于是可以转化为二维数点问题。
建立 Trie,通过 DFS 序抽象为二维数点问题。
做扫描线,考虑每一个 \(r_q\) 做询问右端点时的情况。
先考虑单个下标 \(c_i\)。对于其,我们要维护它最近一次被染色的操作编号 \(x\)。对于 \(l_q > x\),\(c_i\) 没有贡献;否则 \(l_q \le x\),\(c_i\) 的贡献即为 \(v_x\)
对于多个 \(c_i\),即可转化为二维数点,做颜色段均摊 + 树状数组。
如果将 \(c\) 数组的不同下标看作颜色,其实可以发现这是一个和 P1972 [SDOI2009] HH的项链 很像的题目
数据结构启发式合并
现在有若干集合大小总和为 \(n\) 的若干集合,不妨设它们是 \(n\) 个大小为 \(1\) 的集合。将它们合并为一个集合,可以发现合并的过程等价于二叉树结构。如果我们采用启发式合并,等价于操作轻子树。反过来考虑每一个点,它被合并的次数等价于它到根的路径上的轻边数量,而这个数量为 \(O(\log n)\)。故总复杂度为 \(O(n \log n)\)。
例子:并查集按秩合并、平衡树启发式合并(直接用 set/map 做是 \(O(n \log^2 n)\) 的,使用带 finger-search 功能的平衡树则只用 \(O(n \log n)\);但其实不如直接使用 fhq-treap 自带的合并功能。)
注意不能引入与合并无关的操作,否则复杂度会假。
写法用到 map 套 vector。
树上启发式合并(dsu on tree)
一种用于解决子树静态离线点对问题的做法。
reference:自为风月马前卒 - dsu on tree入门
以板题为例:
- 给定一棵有根树,并给定每个点的颜色。试统计每个子树内的不同颜色数。
预处理出轻重儿子,然后 DFS 扫描,统计每种颜色出现次数 \(bjt\) 数组:
-
先递归处理轻儿子,并不计算其对 \(bjt\) 的贡献。
-
递归处理重儿子,计算其对 \(bjt\) 的贡献。
-
二次扫描所有轻儿子的子树,计算其对 \(bjt\) 的贡献。
由于每个节点到根的路径上最多有 \(O(\log n)\) 条轻边,故每个节点最多被遍历 \(O(\log n)\) 次,总复杂度为 \(O(n \log n)\)。
Gemini7X 给的课件写得有问题。这篇题解 写得更好。
初步想法是使用一个桶,这个桶肯定有一维与二进制位是相关的。
观察发现如果满足 \(v_y+d(x, y) \equiv 2^k-1 \pmod{2^k}\),那么再 \(+1\) 后,第 \(k+1\) 位异或结果会取反。为了桶能够记录一个不变值,我们将 \(x, y\) 项分离得到:\(v_y + d(1, y) \equiv d(1, x) \pmod{2^k}\)。
接着再用 dsu on tree 维护这个桶,以及当前异或结果即可。
【实际这个思维过程中的记录使得其“取反”这一条是比较反人类的(正常人肯定是想怎么去记录位是否为 1。。。)。可以学习这一点:记录其是否变化,而不记录其本身。】
启发式分裂
合并的逆向过程。
- 有 \(n\) 个奖杯,第 \(i\) 个奖杯的颜色是 \(c_i\)。 还有一个长度为 \(n\) 的数组 \(f\) ,并且保证这个数列单调递减。一个长度为 \(l\) 的奖杯区间是好的,当且仅当这个区间中的所有颜色的出现次数均不小于 \(f[l]\)。现在他想要找到最长的好的区间,他让你来帮他完成这个任务。
不懂。大概就是区间分裂时尽量选择较短的区间。