省选知识复习

写在前面

一切都要基于分析。 ——— 沃兹·基硕德

知识图

我绝对不会告诉你这是我盗来的

基础算法

CDQ 分治

思想:离线,对序列进行分治。在回溯合并的时候,考虑分治左侧对右侧的贡献。可保证两维有序,再高维的就需要用数据结构维护。

实现:类似于归并排序。注意计算贡献部分和排序部分可能需要分开

线段树分治

思想:离线,对询问建立线段树,然后把修改挂在线段树节点上,最后对树进行 DFS 求解。适用于修改影响一段询问的情况,可以将修改的减法改换为加法

实现:可以建出线段树,然后把修改挂上去;或者直接 DFS 并且划分修改。

wqs 二分

对于一种带有“选择限制”的问题十分有效。例如黑白生成树,度限制生成树等。其思想是,为操作加上额外的花费,以控制选取的数量。当选取的数量符合要求时,我们就可以减去额外花费得到答案。这种算法通常要求没有限制的问题很好做额外花费会导致相关选取方案的变化并且加上限制之后导致原本容易的题目很恶心,就可以尝试 wqs 二分。

这个方法也可以用在 DP 上,作用类似。

基数排序

注意:正着存,倒着取。先低位,再高位。

搜索

meet in middle

思想:折半,对于指数算法尤其有效。要求可以快速查询已知信息

搜索剪枝

随缘qwq。状态少时可以直接哈希记忆化[CQOI]循环赛)。可行性剪枝随手加上,最优剪枝靠分析,搜索顺序可以凭感觉或者......暴力枚举选最优。发现搜索会在一棵搜索树内浪费太久,就用迭代加深。 DLX 用在覆盖问题上面来,需要模型转化虽然我还不会。 A* 算法随缘。

随机化算法

似乎只会退火

随缘算法,对拍调参。退火的时候,用最优解进行迭代,更新的概率函数为\(e^{\frac{-\Delta f}T}\)

遗传算法最好不要碰

数据结构

关于信息维护的本质:

(图源VFleaKing的博客

可并堆

左偏树,合并过程为递归。让较优的作为根进行合并,合并返回之后就维护一下左偏性质。

并查集

一般并查集,单独写按秩合并和单独写路径压缩都是\(O(\log_2n)\)的。

带权并查集,每个节点维护它到根的可合并信息,可以写路径压缩,但是要注意压缩导致的信息合并。合并的时候同样考虑信息合并。

可持久化并查集=可持久化+并查集=可持久化数组+并查集=主席树+并查集。没了

并查集的删除,如果是栈序删除,就可以直接存下来每次操作之后的信息用于回退,此时需要按秩合并。否则就需要用 LCT ,或者建虚点。

0-1 Trie 和线性基

0-1 Trie 可以处理数对的最大异或。线性基可以处理子集的最大异或和

0-1 Trie 可以可持久化,可以合并(就像线段树合并?),可以删除。

线性基本身是一种基于拟阵和线性代数的贪心的数据结构,因此它可以扩展到高维空间上来,比如维护一堆向量,比如这道题

线段树

都是经常用的东西了,应该不容易忘吧

线段树合并就是直接暴力合并,注意它的空间是\(O(n\log n)\)的,并且重合的节点可以新建,也可以直接合并到旧有节点上。如果需要重复利用,就需要新建节点。它一般用在树上(包括自动机的树)。

主席树每次新建节点,动态开点在没有点的时候才新建节点,单次修改的空间都可以认为是\(O(\log n)\)

另外,补充李超线段树,可以维护一次函数,也就可以搞 DP 优化之类的很多东西了

平衡树

主要用的是 fhq-treap 和 splay 。其它的我好像也不会耶......

另一个有用的东西是 Treap ,作为重量平衡树,它可以用于动态标号的问题(没有人的算术,一句话,利用重量平衡树维护“数”的标号,然后用线段树求区间最值)。

笛卡尔树

笛卡尔树是按照权值满足堆性质,按照下标满足二叉搜索树性质的树。当一个序列确定的时候,我们可以\(O(n)\)使用单调栈构建它。

作用:可以配合约束 RMQ 在\(O(n)-O(1)\)的时间内查询区间最值;可以在上面搞 DP

树套树

也就只会 树状数组套主席树,线段树套线段树,线段树套平衡树,等等。

一般而言,使用静态结构做外层结构,动态结构做内层结构。洛谷日报有过专门探讨“结构桃结构”的文章,感觉写得挺好:

还有,一般我们用外层维护序列,内层维护权值。但也不乏有外层维护权值内层维护序列的方法,可以多方面地思考一下。

如果支持离线,还可以用 CDQ 分治、线段树分治、整体二分等方法替换外层结构。我觉得它们可以把树套树吊打一通

bitset

看到 bool 数组的运算就用它算了......

当然,在范围小的时候可以直接状压,比如 CSP2019 的初赛题目。

图论

最短路算法

就那三个,没啥可说的。没有负权边就用 Dijkstra ,有负权边就跑 SPFA (常用 SLF 优化,因为 LLL 写不来)。看到求多点路径之类的问题可以尝试用类似 Floyd 的 DP 方法。

SPFA 还可以在判断负环的时候使用 DFS 算法,并且距离的初值可以赋为 0 而非 \(\infty\)。 SPFA 在判断负环的时候,需要在每个连通分量上面跑一遍,而用 Floyd 的时候就不需要了。

对于存在环形转移的最优性 DP ,我们可以建图并且在上面跑最短路。

最短路可以用于计算差分约束系统的解 —— 对于\(x_i-x_j\le c\)的不等式,它意味着\(x_i\le c + x_j\),这就可以转化到最短路上来做。如果存在负环,系统无解。如果图不保证连通,就建源点向每个点连一条权为 0 的边。

构建最短路图,并在新图上面处理问题,就可以保证某些问题“必须在最短路上”或者之类的要求。例如NOIP2017逛公园

构建最短路树,就可以容易地把关于边的问题分成 树边 和 非树边 ,这样就利于思考处理。而且树的结构更加简洁,还有许多经典的算法与结构可供使用。

关于最长路,它是 NP-Hard 问题,所以看到它,要么是暴力,要么是构造,要么是找规律。

关于 K 短路,懒的话直接 A* 搜索,如果必要就写 可持久化可并堆。 虽然我也不会

其实这类题目很像网络流,难在建图,建完图之后只带模板,幸运的是,它没有网络流灵活

强连通分量,割点,割边

都是 Tarjan ,代码全文背诵。

有向图,缩点之后就变成了 DAG ,这样就方便考虑 DP 、博弈或者奇怪的贪心。

无向图,按照边双连通分量缩点之后就变成了树。树上就有许多现成的算法和结构可以用。比如,有的题目要求你动态维护边双连通分量(加边操作,如果有删边操作可能需要倒着处理),就可以缩点之后用 LCT 维护一下树。

缩点毕竟是一种途径,把比较复杂的图转化为可做的、简洁的图,然后再思考处理。

2-SAT

拆点\(x,\neg x\),建图(用一条有向边表示连带关系)表示题目的要求,然后 Tarjan 。如果对立点在一个强连通分量里面,那么 2-SAT 无解。否则可以构造一组解出来。

如果需要用边表示一种“不存在的、不可能的情况”,可以直接从\(x\)连向\(\neg x\)或者相反。这样的方法在用 2-SAT 求解逻辑方程()的时候有所应用。

2-SAT 的一般解可以直接用拓扑序构造,选取\(x\)\(\neg x\)所在强连通分量拓扑序较大的那个,也就是 Tarjan 取得的强连通分量中标号较小的那个

如果需要求字典序最小等特解需要 DFS 。

二分图对偶问题

背诵内容如下

最大匹配(最大流) = 最小点覆盖 = 总点数 - 最大独立集

最大权匹配(最大费用最大流,取反边权得到最小费用最大流)= 最小权点覆盖 = 总权和 - 最大权独立集

网络流

关于模板:就用 Dinic 吧,方便背诵,其它的我也不会

建图方面,主要是靠分析和经验,瞎蒙也可以,可以参考网络流 24 题的模型,再丢个链接。一些经典的思想包括:建立源汇,拆点,补集转化(最小割),作为二分/枚举的内层结构,构造点的含义,网格图黑白染色距离限制(HNOI切糕各种方式优化建图 ......

关于平面图,在上面做最小割可以发现,割边实际上可以理解为拆边使得两个面相连,割的结果是平面图内部的面变成一个面,并同外部相连。那么就可以考虑建对偶图。此时对偶图的一个点就代表了它的一个面,一条边就代表了原图的一条边(割掉)。如果稍微改一改对偶图(增加源汇)就发现,它的最短路就是原最小割,即原最大流。

除了一般的网络流算法之外,还有奇葩的模拟费用流。我就是不会

上下界循环流:抱佛脚

(图片来自liu_runda的博客

有源汇上下界网络流、最大流:抱佛脚

(图片来自liu_runda的博客

最小费用可行流:抱佛脚

(图片来自符拉迪沃斯托克的博客

对偶图

(图片来自StarHai的博客

生成树

最小生成树就是瓶颈生成树,瓶颈生成树不一定是最小生成树。

最小比率生成树就是 0-1 分数规划的问题。对于边\(i\),选择它会导致\(c_i\)的代价和\(b_i\)的收益,找一颗生成树\(T\)使得\(f(T)=\frac{\sum_{e\in T} b_e}{\sum_{e\in T} c_e}\)最小。 0-1 分数规划的通用解法是二分检查,设二分值为\(x\),那么我们需要检查是否有生成树\(T_0\)满足\(f(T_0)\le x\Rightarrow \sum_{e\in T_0} (b_e-xc_e)\le0\),对边重新赋权然后求最小生成树判断正负即可。

最小树形图......好像没人考耶......算法好像不难,看看吧?

度限制生成树可以用 wqs 二分,控制“连接\(s\)的边的代价”。肯定代价越大,\(s\)度数越小,反之\(s\)度数越大,所以最终可以使得\(s\)的度为\(k\)

Matrix Tree 定理(要求无自环),先丢几个结论吧:定义\(G\)为图的邻接矩阵,\(D\)为图的度数矩阵,\(K\)为图的基尔霍夫矩阵:

\[\begin{aligned} A_{ij}&= [(i,j)\in E]\\ D_{ij}&= \begin{cases} deg_i & i=j\\ 0 & i\not=j\\ \end{cases}\\ K&=D-A \end{aligned} \]

那么这个图的生成树数量就是\(K\)的任意\(n-1\)阶主子式的行列式

拓展:带权。令\(T\)的权为\(\prod_{e\in T}w_T\),定理可以求所有生成树的权的和。方法是拆边——将权看做重边数量,那么原来一棵生成树的方案数就变成了权值和。故有:

\[\begin{aligned} A'_{ij}&= \begin{cases} w_{ij} & (i,j)\in E\\ 0 & (i,j)\not\in E \end{cases}\\ D'_{ij}&= \begin{cases} \sum_{(i,v)\in E} w_{iv} & i=j\\ 0 & i\not=j \end{cases}\\ K'&=D'-A' \end{aligned} \]

然后方法一致。

拓展:有向树形图。\(A\)为有向图的邻接矩阵。如果是外向树,则\(D\)入度矩阵;如果是内向树,则\(D\)出度矩阵\(K\)定义不变。如果\(r\)为根,那么就要计算删除第\(r\)行第\(r\)列之后的主子式的行列式该部分可与上面所述一起使用

总结一下:\(A\)\(D\)控制了边权和哪些边会被使用\(K\)的主子式类型控制了根的位置

树剖

本质上树剖就是特殊的 DFS 序。其思想在于将树上问题转化为序列上问题,并带来一定的时间消耗。树剖运用的是重链剖分,会有\(O(\log_2n)\)的花费。一般而言,树剖维护的信息都是可合并的,解决的问题也是可拆分可合并贡献的。说着这些你应该想到了......结构套结构。确实有这样的题目,而且我感觉树剖就是把序列上的毒瘤题目通过重链剖分的方法挂到了树上去

所以,树链剖分的维护结构应该不限于线段树,而是应该还有分块,平衡树,树状数组,vector等支持维护序列的玩意儿。

顺带一说,树剖是支持换根的,只需要考虑新根到原根的路径上的贡献变化即可。

绝大多数树上 DFS 序问题其实和树剖很相似,只不过 DFS 序相对随机,所以常用于解决子树问题,没法解决链问题。

树剖也可以用于优化树上路径相关的图论题目,常常跟树上倍增两个抢地盘。树剖的优点就是点少,边虽然多些,但是卡不满。

顺带一提树上倍增,主要用于查询 LCA 和路径信息。对于每次添加叶子的操作它也可以很快地维护更新。优化建图方面,它是点多边少。

动态树

我所知道的也就两个算法, LCT 和 ETT 。 LCT 处理链上信息极为给力,但是处理子树信息就比较复杂,处理难以删除的子树信息就很鸡肋。 ETT 擅长处理子树信息,勉强能处理一些链信息,但是并不那么动态。

同上,这些算法的本质也是将树上问题转为序列问题(区间问题),然后通过一般方法解决。

随缘吧

树上分治

点分治,边分治, dsu on tree 常常用于解决“距离,长度”等与树上路径有关的统计性问题。偶尔也会涉及到树上连通块Shopping)。

然后好像就没啥了

如果遇到了统计性问题还加了个修改和多次询问......那就是点分树没跑了。

upd : 刚刚写了一发板子,一定要把已经分治过的点判掉!

prufer 序列

生成:每次取编号最小的叶子,将它的邻接点塞入序列,然后删除它。最后两个点直接丢进序列。

构造:每次取出 prufer 序列头,然后找出最小的未使用过的,未出现在序列中的点,两者连边,删除序列头。最后两个点单独连边

可以用来求\(n\)个点的有编号无根生成树的个数,即\(n^{n-2}\)个,有根的就有\(n^{n-2}\times n = n^{n-1}\)个。可以拿来配合\(set\)造数据。

如果度数有限制\(d_1,d_2,d_3,...,d_n\),方案数就是\(\frac{(n-2)!}{\prod_{i=1}^n (d_i-1)!}\),因为度数为\(d\)的点会在序列中出现\(d-1\)

数学

以下均设:

  • \(P\)是素数集合。

  • 对于数论函数\(f(n)\),令\(S_f\)\(f\)的前缀和。

Miller-Rabbin 和 Pollard-Rho

Miller-Rabbin 是使用费马小定理\(\forall p\in P, x^{p-1}\equiv 1\pmod p\),和二次探测定理\(\forall p\in P, x^2\equiv 1\pmod p\Rightarrow x\equiv 1\pmod p\lor x\equiv p-1\pmod p\)进行素数猜测性验证。

对于\(n\),一轮验证的过程是:设\(n=m\cdot 2^k+1\);随机一个数\(A\);检测是否对于所有\(1\le i\le k\)\(A\)\(\bmod p\)的意义下都满足二次探测;检测\(A\)\(\bmod p\)意义下是否满足费马小定理。

大概做\(10\)次,盘错的概率就会降到\(4^{-10}\)左右......

Pollard-Rho 是随机化的大数分解算法,使用伪随机序列\(a_i\equiv a_{i-1}^2+c\pmod n\)来生成一些随机数,并且将两项之间的差来与\(n\)\(\gcd\),尝试得出一个因子。

容易发现,直接做的话,伪随机序列很容易掉到环里,因此会使用 Floyd 判环法进行判环。简单来说,给定两个指针,一个一次走一步,一个一次走两步,当两个指针再次相同的时候,必然有一个指针被套圈了,那么就出现了环。当\(\gcd\)函数调用次数太多的时候,我们可以考虑压缩样本。根据欧几里得算法,我们可以将多个样本乘起来\(\bmod n\),达到压缩的目的,并且不影响正确性(\(\gcd(i,j)=\gcd(ia\bmod j, j)\))。有时我们也可以使用倍增方法:

块速幂和快速乘

块速幂可以解决底数相同,指数不同的多次询问幂问题,即多次询问\(a^k\bmod p\)根据名称可以知道,我们只需要\(m=\lceil\sqrt p\rceil\),然后预处理出\(a^0,a^1,a^2,...,a^{m-1},a^m,a^{2m},a^{3m},...,a^{m(m-1)}\)即可。查询的时候,先用欧拉定理把指数化小,再找出两个对应的幂凑出答案就行。时间\(O(\sqrt p)-O(1)\)

快速乘是有时候模数太大(爆 long long 的那种)时的乘法优化。代码如下:

typedef long long LL;

LL mul( const LL a, const LL b, const LL mod )
{
	return ( a * b - ( LL ) ( ( long double ) a / mod * b ) * mod + mod ) % mod;
}

(EX)CRT

该方法用于解决同余方程:

\[\begin{cases} x\equiv a_1\pmod{p_1}\\ x\equiv a_2\pmod{p_2}\\ \dots\\ x\equiv a_n\pmod{p_n} \end{cases} \]

\(p\)两两互质的时候,构造解:\(M=\prod_{i=1}^n p_i,M_i=\frac{M}{p_i},\overline{M_i}\equiv M_i^{-1}\pmod {p_i}\),然后解\(x\equiv \sum_{i=1}^na_i\cdot\overline{M_i}\cdot M_i\pmod M\)

\(p\)不一定两两互质的时候,考虑合并两个方程:

已知前\(k-1\)个方程合并后:\(M=\text{lcm}_{i=1}^{k-1}p_i\),存在解\(x+tM\)。考虑合并第\(k\)个方程,即:

\[x+tM\equiv a_k\pmod {p_k}\Rightarrow tM\equiv a_k-x\pmod {p_k} \]

然后就可以尝试解出\(t\)。如果没有解,整个方程就无解。设\(M'=\text{lcm}(M,p_k)\),新的解就是\(x+tM+kM'\)

二元一次不定方程

即:

\[ax+by=c \]

可以用扩展欧几里得求解。设\(d=\gcd(a,b)\),如果\(c\)不是\(d\)的倍数,无解。算法会得到\(ax'+by'=d\),然后转化得到\(\begin{cases}x_0=\frac{cx'} d\\y_0=\frac{cy'}{d}\end{cases}\),解出来的特解是\(\begin{cases}x=x_0\\y=y_0\end{cases}\),通解是\(\begin{cases}x=x_0+t\times \frac b d\\y=y_0+t\times\frac a d\end{cases}\)

(EX)BSGS

求解离散对数问题。即给定\(n,m,p\),找到最小的\(k\),使得\(n^k\equiv m\pmod p\)

\(p\)为质数的时候,考虑令\(t=\lceil\sqrt p\rceil\),就有\(k=tx+r,0\le r<t\)。即:

\[n^{tx+r}\equiv m\pmod p\Rightarrow n^r\equiv m\times n^{-tx}\pmod p \]

把所有的\(n^r\)都丢到一个哈希表里面,再枚举\(t\),到表里进行查找即可,时间复杂度\(O(\sqrt p)\)

\(p\)不为质数的时候,根据同余性质,发现可以直接把\(\gcd(n,p)\)除掉,再进行普通 BSGS 。令\(d=\gcd(n,p)\),有:

\[n^{k-1}\times \frac n d\equiv \frac m d\pmod {\frac p d}\\ n^{k-1}\equiv \frac m d\times \left(\frac n d\right)^{-1} \pmod {\frac p d} \]

做一下变量代换就可以得到离散对数的原本形式。如果此时还有公因子就继续操作,如果没有就直接上普通 BSGS 。需要注意,如果中途就直接把\(m\)除干净了,需要特判;设我们进行了\(x\)次清理公因子的操作,我们需要检查是否有\(0\le i<x,n^i\equiv m\pmod p\)

快速筛法

杜教筛

利用狄利克雷卷积的构造筛法。对于积性函数\(f(n)\),构造积性函数\(h(n),g(n)\),使得\(h=f*g\)

考虑求\(h\)的前缀和:

\[\begin{aligned} \sum_{i=1}^n h(i) &= \sum_{i=1}^n \sum_{d|i} g(d)\times f(\frac i d)\\ &= \sum_{d=1}^n g(d)\times S_f(\lfloor\frac i d\rfloor) \end{aligned} \]

\(S_f(n)\)提出来:

\[\begin{aligned} &\Rightarrow S_h(n)=g(1)S_f(n)+\sum_{d=2}^n g(d)\times S_f(\lfloor\frac i d\rfloor)\\ &\Rightarrow g(1)S_f(n)=S_h(n)-\sum_{d=2}^n g(d)\times S_f(\lfloor\frac i d\rfloor) \end{aligned} \]

然后你只需要可以快速求出\(h\)\(g\)的前缀和,并筛出较小的\(S_f\),就可以用杜教筛筛出大的\(S_f\),时间复杂度是\(O(n^{\frac 2 3})\)配合记忆化使用

当需要多次询问某个\(S(\lfloor\frac n d\rfloor)\)的时候,杜教筛就很好用,因为它可以一遍筛就把所有这些点都算出来。

min_25

posted @ 2020-06-16 12:20  crashed  阅读(211)  评论(1编辑  收藏  举报