一个暑假两次集训,感觉学了好多好多的东西,也挖了好多好多的坑,于是就决定写一篇关于算法的总结,用于熟悉新算法,也留下一点对新算法的理解。
AC自动机
简单的说就是在\(trie\)树上实现\(KMP\),用于多模式串的匹配。
而\(AC\)自动机的理解就在于\(fail\)指针,\(fail\)指针是实现多模式串匹配的关键。每次匹配失败后,\(fail\)指针会指向一个当前匹配串的最长真后缀,数次跳跃后,就能实现对文本串的一段在\(trie\)树进行所有可能的匹配。
而对于\(fail\)指针的求解,用\(bfs\)实现。\(AC\)自动机的时间复杂度是线性的(一般情况下)。
好像\(AC\)自动机还有一个拓扑建图优化,还没有学。
manacher算法
\(manacher\)算法用于求解一个字符串每一个位置的最长回文字串。
这个算法其实和\(KMP\)的思想差不多,只要用已知信息去推后面的信息就可以了。具体地,可以记录当前最大的回文右端点,然后画一下对称图,就可以推了,不是很难。
博客这篇,模板题这里,可以看\(HL\ Day4\)的课件。
ExKMP算法
\(KMP\)算法的拓展,又称\(z-algorithm\),用于求解以下问题:
给你两个字符串\(s,t\),长度分别为\(n,m\),请输出\(s\)的每一个后缀与t的最长公共前缀。
思想和\(KMP\),\(manacher\)都是一样的,就是从前往后推。由于相邻两个位置的答案有很大一部分是重复匹配的,所以可以根据上一次的答案考虑,需要分两种情况讨论。
不是很好写,好像下标还只能从\(0\)开始,从\(1\)开始会非常别扭。
块状树
把分块思想放在树上,就是树分块。
用\(dfs\)遍历树,把连续遍历到的一段节点加在一个块里。然后就可以对树子树信息进行维护了。
还是和分块一样,块内设法直接维护,边角暴力。这样进行分块是可以支持修改和动态加点的,但是不是稳定算法,会被菊花图卡掉。
模板题这里。
树同构
也可以说是树哈希算法,主要用于判定树的形态。
一般来说用的是最小表示法,像树形\(dp\)一样,先获取每个子树的\(hash\)值,然后组合成一个新的序列,再\(hash\)一个值返回即可。
当树是无根树的时候,还要先找以下重心作为根,当然也要考虑双重心的情况。
模板题这里。
类欧几里得算法
好像和欧几里得算法没什么关系,是用来计算整除求和式的。
主要思想就是分类,然后分治,比欧几里得算法稍微复杂一点,就是推公式吧,套路还是拆一下求和式,下取整。
模板题这里。
整体分治
简单的说就是对于多个询问同时进行二分答案,像二分答案一样判定询问的真实答案位于哪一侧,然后对询问进行分类,就变成一个分治算法了。
真正理解点在于询问分类,这是分治算法的核心,得到的子问题是独立的。这样就可以理解为什么修改操作也能参与分类了。
代码上就是要想好用什么数据结构来维护贡献,然后注意操作的时间顺序就可以了。
模板题:K大数查询,Dynamic Rankings,可以看蓝书。
线段树分治
和\(cdq\)分治一样,是时间分治的一种。
就是对时间建线段树,然后把一个时间段内存在的操作打在线段树的\(log\)个区间节点上。而对于一个时间点的询问,就对应了线段树的一个叶节点,所以只需将询问挂在叶节点上即可。
在线段树上处理完所有操作和询问后,对整棵线段树\(dfs\)一次,向下访问时依次处理掉节点上的操作,到达叶节点就回答掉询问,向上回溯时就撤销掉操作。
别的都很简单啊,想好用什么数据结构维护操作(要支持撤销)即可。
后缀数组
实际上是后缀排序,只学了\(O(nlog_2n)\)倍增法。
其实倍增就体现在排序的长度上,每一次排序只根据每个后缀的前二的幂次为来排一个相对位置,在\(O(log_2n)\)次排序后就能得到最后的排名。
每一次当中如何排序呢?用双关键字的基数排序,时间复杂度的\(O(n)\)的。我们取\((rank1_i,rank2_i)\)作为排序的双关键字,\(rank1_i\)指的是后缀\(i\)上一次排序后的排名,而\(rank2_i\)指的是后半部分后缀上一次排序后的排名,相当于倍增\(2^k\)过程中一个管辖前\(2^{k-1}\)个字符,一个管辖后\(2^{k-1}\)个字符。
如果是\(sort\)的话,这样就已经可以了,但是时间复杂度是\(O(nlog^2n)\)。
基数排序如何实现?我们还得记一下\(pos_i\):第二关键字排名为\(i\)的后缀位置。这样就可以在基数排序的时候定位第二关键字了。
\(height\)数组怎么求,最重要的性质就是\(height[rank[i]]\geq height[rank[i-1]]-1\),然后就可以利用单调性求了。
后缀数组好像有很多经典的运用,还没有学完。
快速傅里叶变换
最直接的运用就是计算多项式乘法。
很简单的思路,系数表达法做乘法太慢,就用点值表达法做乘法。于是想办法搞一个算法可以在\(O(nlog_2n)\)的时间内在系数和点值之间做转换,就是\(FFT\)在做的事情。
思路是分治,\(n\)个点我们带入\(n\)个不同的单位根,我们发现当对多项式根据次数奇偶性分类后,可以计算一半得到另一半的值,于是就能递归下去了。
最后的优化就是把递归省了,发现可以用二进制的\(bit-reverse\)来搞。
快速数论变换
发现把\(FFT\)的单位根换成原根,折半引理和消去引理照样成立,于是就有了模意义下的\(FFT\)。
分治FFT
要算一个卷积,但是发现卷积计算式中有一项和我们未计算的值有关。
于是可以想到\(cdq\)分治,每一次尝试计算左边对右边的贡献即可。计算贡献时,发现还是一个卷积,\(NTT/FFT\)即可。
Edmonds-Karp增广路
求网络最大流。
先是\(FF\)算法的思想,找到一条增广路就增广。但是这样时间复杂度是不对的,上限可达\(O(m|f_{max}|)\)。
可以证明,每次找最短路径增广路,增广次数不会超过\(O(nm)\)次,然后\(EK\)增广路就诞生了。
模板题这里,博客已经写了。
dinic算法
\(EK\)增广路的优化。
多数时候最短路径增广路不止一条,于是就能想到多路同时增广。实现的时候还要加上若干剪枝,就可以证明时间复杂度是\(O(n^2m)\)的\(dinic\)算法了。
模板题这里,博客已经写了。
费用流
没什么意思,把最大流的最短路径增广路用用最短路算法跑一下就可以了。
一般来说直接\(SPFA\)即可,加个\(johnson\)算法的势函数优化也可以用\(dijkstra\)。
还有一种思路就是学\(KM\)算法的改顶标,只需满足三角形不等式即可。这样的\(zkw\)费用流在稠密图上效率比较高,就是不能跑负权图(最大费用最大流)。
2-sat模型
就是考虑命题之间的推导关系,在图上连的是有向边,当然逆否命题之间也要连边。
然后考虑矛盾性,只要用\(tarjan\) \(SCC\)缩点即可。如果要输出一组可行解,只需自底向上执行拓扑排序即可。当然,利用\(tarjan\)的性质可以很方便的写出代码。
看蓝书吧。
二分图
先是二分图判定,可以用染色法,不断给相邻节点标不同的颜色,然后看一看哟没有冲突即可。
二分图匹配,可以直接跑最大流,或者用匈牙利算法。匈牙利算法的本质就是不断找增广路,找到了就对路径上的边取一下反,这样最大匹配的大小就会增加\(1\)。
二分图带权匹配,可以直接跑费用流,也可以用\(KM\)算法。\(KM\)算法的思路就是通过合法修改顶标的手段不断扩大相等子图。不过,\(KM\)算法只有在带权最大匹配是完备匹配时才能用。
Link-Cut-Tree
要维护一棵树的链信息,需要支持动态加边,删边。
方法是链剖,不过是实链剖分,要用一堆\(splay\)来维护。核心操作是\(access\),如何理解?虚实链其实是自己定义的,我们只要维护好打通一条实链的正确\(splay\)森林即可,这样链信息就是可以正确维护的,并且时间复杂度是\(O(log_2n)\)。
操作有点多,但是码量不大,需要注意一些细节,在完成代价为树高的操作时别忘了\(splay\)一下保证时间复杂度。
后缀自动机
一个很鬼畜的自动机,\(endpos\)等价类维护成一个状态,字符维护成转移,再加一些后缀链接就以高度压缩的形式维护了一个字符串的所有子串信息。
难点在于构建,其实核心就是那三种情况的分类讨论。而最难理解的就是第三种情况,其实\(clone\)一个点的本质就是一个\(endpos\)等价类的两个子串一个不属于同一个\(endpos\)等价类了,必须分裂一个状态才能正确地维护后缀链接。
用法好像有很多,还是很会,但是代码挺短的。
杜教筛
求积性函数的前缀和。
没啥神奇的,就是对一个函数做一下狄利克雷卷积,发现推出了一个子问题,还可以用整除分块来做,于是就变成了记忆化搜索。
可以用线性筛先预处理一些值,然后再记忆化搜索时间复杂度就是\(O(n^{\frac{2}{3}})\)的。
杜教筛的时间复杂度瓶颈是整除分块,所以求狄利克雷卷积的另外两个数论函数的时间复杂度要求是根号的,不一定要\(O(1)\),甚至还可以套杜教筛。
模板题这里,可以看\(R\)爷课件。
Miller-Rabin 算法
素性判定的\(RP\)算法,时间复杂度\(O(log_2n)\)。
两个步骤,费马小定理和二次探测定理,没什么好说的,当取\(10\)个质数的时候出错率几乎为\(0\)。
Pollards'Rho 算法
基于\(Miller-Rabin\)的质因子分解算法,期望复杂度\(O(\sqrt[4]{n}log_2n)\)。
原理其实是基于生日悖论,每次用两个随机数的差尝试分解成功概率更大。而随机数的生产就是用\(x_i=x_{i-1}^2+c\)的形式,保证分布均匀。
随机数可能尝试模意义下的环,就用倍增标记的方法判就可以了。
模板题这里。