【考试】信竞思维引导体系

NCC79601

2019-09-13 23:14:59

Personal

$$\huge\textbf{信竞思维引导体系}$$ **前言** 信竞知识点也差不多要学完了,得整理整理思路了。碰到一个题目也得从不同的方面去考虑;能简化的就简化,千万不要为了一些无意义的东西伤脑筋。算算复杂度也就大概知道可能用到哪些算法了。 # 搜索 ## 暴搜 包括 bfs / dfs ,用于小数据骗分 / 标程对拍 std 代码 一般 dfs 应用范围大于 bfs ## 剪枝技巧 ### 优化搜索顺序 改变搜索顺序,使得潜在状态量以最快速度减少(靶形数独) ### 等效冗余 如果两个状态导致的结果相同,则只选择最优的状态进行扩展(引水入城 / Mayan 游戏) ### 可行性剪枝 ### 最优性剪枝 其实就是$A^*$的弱化版,记录到达某个状态的最小花费,如果某次搜索时发现开销比以前大,则剪掉搜索枝 ## 双向广搜 用于时空开销非常大,但对半砍(Meet-in-Middle)后就可接受的搜索题目 一般用于$O(8^{16})$ 变 $O(2\times8^8)$的题目 ## 迭代加深搜索 用于根本不知道范围 / 无法获知搜索上限的题目(如埃及分数) ## 启发式搜索 用于可以写出$f(n)=g(n)+h(n)$,其中$h(n)\leq h^*(n)$的估价函数的搜索 可处理$k$短路 / 一些与曼哈顿距离有关的路径问题 还可处理可乐观估计剩余步数的图形 / 数字变换问题 另外可以用启发式搜索理论优化迭代加深搜索 ## 记忆化搜索 用于数位 dp / 某些统计方案数的问题 # 图论 常用方法:建反图,建虚点,边排序 ## 最短路 ### Dijsktra 稠密图性能较佳,但碰上完全图就不能够加堆优化(否则会突然变慢) 不能判负环 ### SPFA 性能逊于 Dijsktra,但支持判断负环 可用于差分约束系统(线性不等式与松弛方程类比) ### Floyd 如果 MAXN = 50,那还是用它吧 本质思想类似动态规划 ### 启发式搜索 由$h^*(n)=dis[n]$,直接进行启发式搜索即可(不过需要先建反图跑一次最短路,然后才能用A*),常用于解决$k$短路 / 其他动态统计最短路径的问题 ### 最短路树 由最短路构成的树,常用于解决一些询问断边后最短路变化情况 / 统计最短路上的点的信息的问题 ## Tarjan ### 缩点 把一个图缩成 DAG 然后跑其他算法,常用艺能 ### 割点 与图的连通性有关 ### 割边 用于无向图缩点,把所有桥断掉以后跑连通块得到的就是 E-DCC ## 最小生成树 还有最大生成树,最小生成森林(建超级根) ### Prim 每次找的是最小可扩展边 用得比较少,好像还更慢,所以不想管了 ### Kruskal 用并查集维护连通块 跑得很快,用得多一些 ## 并查集 / 带权并查集 有些时候,把问题熵值降低就可以直接用(带权)并查集维护连通性了(比如 MooTube) 带权并查集的权值可以是许多东西(比如最小 / 最大值,节点权值和,甚至连通块直径),视题目而变 ## 分层图 这个估计用不上,但是出现了类似“在图上共能进行$k$次操作”的题目就可以水掉 ## 二分图染色 / 二分图匹配 用以解决两两配对问题 练习得较少,但遇到题目必须反应过来 ## 欧拉回路 用于解决一笔画问题,所谓的边有可能是字母对 / 数字对等等 也可解决一些入度与出度相等点数的问题 # 树 ## 构造 如果一个元素直接与三个元素有关,并且有一个元素较另两个元素特殊,那么可以把它们构造成一棵二叉树(比如跳跳棋) 在用遍历顺序来逆推树的结构时,必须知道中序遍历 ## 倍增 主要应用于 LCA 上 偶尔也有倍增树形结构 ## LCA 只要是$log$级别的算法都可能被魔改到 LCA 身上(又是跳跳棋) 就一般的树来说,树链剖分的 LCA 要快于倍增 还有用 ST 表实现的$O(1)$查询写法 ## 树上差分 求解树上两个节点的距离 加速树形 dp 的前缀状态计算 ## 直径 / 重心 两树合并时的新直径问题 两树合并时的新重心问题 ## dfs 序 / 树链剖分 将树按照 dfs 顺序映射到一个区间上 解决子树 / 两点间路径修改 / 查询问题 # 数据结构 ## 链表 / 双向链表 快速区间删除 / 维护节点连续性问题 ## 单调栈 查询某元素向左 / 右遍历时第一个比它大 / 小的元素 ## 单调队列 处理滑动窗口 / 定区间最值查询问题(可以用到动态规划中) 二维单调队列处理矩形内最大 / 最小元素问题 维护斜率优化中的凸包 ## 优先队列 用于降熵后的贪心优化 自定义运算符的优先队列处理复杂模拟问题 优化 Dijsktra 中对最小边的查询 双优先队列维护定点两侧元素的最大 / 最小元素 ## 手写堆 支持堆的合并操作 维护 Treap 的性质 ## Hash 表 $O(1)$查询字符串 / 巨大数字是否存在 可同时维护元素出现的次数等信息 ## ST 表 一般不可修改,但支持$O(1)$区间查询 查询时跑得飞快,可以用来优化掉某些非正解的$log$从而跑到正解的速度 还可以用来加速 LCA 的查询 ## 树状数组 $O(logn)$单点修改,$O(logn)$区间查询 一般情况下比线段树快很多,可在魔改之后进行区间修改 / 区间查询,不过那还不如直接打线段树来得痛快 将树状数组看作一个桶,可以用于区间内元素个数的计算(数字不大太的逆序对问题) ## 线段树 $O(logn)$区间修改,$O(logn)$区间查询 全能性很强,可用于维护各种只与端点有关的可合并信息(如:直径,扫描线等) ### 动态开点线段树 又名值域线段树 / 权值线段树 某些题目中可看作一个动态桶,用于存储不进行离散化的元素信息 可以用指针来写,每次删除元素以后把空间 free() 掉即可避免内存爆炸 ## 字典树 和哈希类似,用于查询字符串的存在问题 同样可使用指针和 free() 节约内存 ## 平衡树 大部分的题目都可以用 set / multiset 水掉 如果碰上了 set 无法魔改 / 需要查询元素出现次数的情况,还是老老实实打非旋 Treap 稳一点(替罪羊过于玄学,Splay 易写错还更慢) # STL 相关 使用 STL 时一定要注意容器是否已空,否则会 RE ## set / multiset 这两种容器是可以用迭代器按顺序遍历内部元素的,所以用处比较大 寻找冲突元素时可以采用重载运算符的方法(注意:在 set 当中,若$a\not <b$且$b\not<a$,则$a=b$),直接使用 s.find() 函数处理 set 还可以用来动态维护第$k$小元素,直接开个迭代器指向第$k$小元素即可,新加入元素不会对迭代器指向产生影响 ## vector vector 实际上是很慢的,效率远不及数组,所以在图论当中需避免使用 vector 来存边,还是开链式前向星比较好 在哈希表当中用处最大,作为动态容器储存每个哈希值对应的元素 ## map 本质上是个平衡树,容器内元素越多效率越慢 广泛应用于搜索判重 / 查询复杂对应关系(string 到 int 可以用更快的哈希) 使用自定义结构体做下标时需要重载小于号 ## bitset 把一个数拆成二进制,比如 bitset<20> b(56) 表示开个$20$数位的二进制数存放$56$这个数字 输出时用遍历 cout 即可 做状态压缩的时候可以用来调试 # 动态规划 动态规划的核心是状态描述和决策转移,难点是复杂度优化 有些时候可以通过 $2e7$ 反算出需要优化掉哪些枚举,这样会使得优化更有目的性 ## 背包问题 ### 01背包 最简单的背包问题,为背包开桶进行动规 对容积的枚举是倒序的(物品唯一,不可叠加) 如果桶会爆炸,可以用模拟退火进行随机化处理 ### 完全背包 常见于无限货币问题(付钱 / 找钱) 对容积的枚举是正序的(无限物品,可叠加) 可以用于优化某些情况下的多重背包方案数问题 ### 多重背包 常见于有限货币问题 / 商品问题 可用二进制优化将物品数降至$log$级别,但无法处理方案数问题(完全背包 + 加法原理解决) ### 分组背包 每组物品只能选一件,那么用$f[i][j]$表示考虑前$i$组,背包容积为$j$时的状态 枚举时,对容积的枚举放在对组内物品的枚举之外,才能保证每组最多选一个物品 ### 树上背包 如果碰到树形依赖关系的背包问题,可用$f[i][j]$表示以$i$为根的子树选$j$个节点 / 容积为$j$时的状态,转移方程类似 $f[u][i]=\max\{f[u][i-j]+f[v][j]\}$ ## 树形动规 常用方法:$[0/1]$存放选与不选,对森林建超级根连接所有根(若有基环树还需要缩点) 用$f[i][\cdots]$来表示考虑以$i$为根的子树,一般是边 dfs 边转移状态 ## DAG 动规 一般缩点以后变成 DAG,就可以用 DAG 动规(类似 SPFA 思想) DAG 动规过程实质上是个拓扑排序的过程 ## 记忆化搜索 一般就是数位 dp 需要观察该如何描述一个数字的特征,每个特征分别用一维来存储 ## 最长单调子序列问题 有$O(n^2)$做法和$O(nlogn)$做法 $O(n^2)$做法是用$f[i]$表示以$a[i]$结尾的最长单调子序列长度,然后每次枚举前缀状态(兼容性更好) $O(nlogn)$做法是用 upper_bound() / lower_bound() 以及贪心思想优化前缀状态的枚举 变形后还可以用于求两个字符串的最长公共子串长度(最长公共子序列) ## 状压动规 如果遇到$n=10,n=12$之类的题就大概率是用状压 可以先把可行状态装到内存池中,节省枚举时间 ## 区间动规 用$f[i][j]$表示$[i,j]$这个区间的状态,一般后边都会附着一维$[0/1]$来表示当前在左端点 / 右端点 有些时候第一层 for 循环需要枚举区间长度以保证前缀状态已知 还可以变形为$f[i][j]$表示一个东西的$[1,i]$区间到另一个东西的$[1,j]$区间的状态(比如字符串变换问题) ## 斜率优化 若发现 dp 方程可以整理为$b=y+kx$的形式,则可用单调队列维护一个凸包以优化寻找最小前缀状态的过程 有些时候数组可以滚动,但别把自己滚死了(比如反向 memcpy) ### 费用提前 遇到有后效性的费用计算时,为了兼容斜率优化,把后面的费用提前到现在计算以取消后效性 ## 路径压缩动规 如果描述状态会导致空间爆炸,那就可以凭信仰进行路径压缩 一般路径压缩可能会用到 LCM 之类的东西 ## 四维动规 $O(n^4)$解决在一个矩形内走出两条单调路径的问题 用$f[i][j][k][l]$描述一个人走到$(i,j)$,另一个人走到$(k,l)$的状态,再根据题目要求适当调整 ## 齐线法 用于处理最大合法矩阵问题 用$l[i][j],r[i][j],h[i][j]$表示合法的最大可扩展宽度 / 高度,然后$O(n^2)$跑一遍所有的点统计答案 # 数论 ## 差分 快速区间修改 处理一些贪心 ( NOIP 2018 D1T1 ) / 闭合匹配问题 ## 前缀和 快速询问区间和 应用容斥原理的二维前缀和快速询问二维区间和 运用前缀和思想优化部分动态规划(费用提前思想) ## 辗转相除法 求解两个数的最大公约数 / 最小公倍数 加速某些具有更相减损特性的计算(比如跳跳棋) ## GCD / LCM 的性质 设$gcd(a,b)=g,\;lcm(a,b)=l$ 则有$gcd(a/g,b/g)=1,\;gcd(l/a,l/b)=1$ ## 因数枚举 $O(\sqrt{n})$枚举一个数字的所有可能的因数 注意如果一个数字$x$是$n$的因数,那么$n/x$也是$n$的因数,所以枚举时要注意处理这种情况;还要特判$\sqrt{n}$是否被多算一次 ## 线性筛法 用桶筛质数 获得一个区间内所有质数 ## 扩展欧几里得算法 (exgcd) 求解$ax+by=gcd(a,b)$方程 求解一个数在与其互质的数意义下的逆元(除法取模) ## 费马小定理 若$a,p$互质,则$a^{p-1}\equiv 1(\mod p)$ 应用于逆元的计算,$a$在模$p$意义下的逆元即 $a^{p-2}$(可用快速幂加速) ## 中国剩余定理 求解线性同余方程组 ## 欧拉函数 遇到求区间内互质对个数的时候可以用上 ## 快速幂 使用二进制加速求解一个数的$p$次幂 ## 矩阵 / 矩阵快速幂 加速求解线性递推式第$n$项 ## 逆元递推 获得一个区间内所有数的逆元 可以设 $p=k\cdot i+r$进行推导,推导过程就是由 $k\cdot i+r\equiv0$然后两边同乘$i^{-1}$和$r^{-1}$,这里记住结论就可以了:$i^{-1}=(p-p/i)\cdot(p\mod i)^{-1}(\mod p)$ 实际上还可以用于逆元递归,$x^{-1}=(p-p/x)\cdot(p\mod x)^{-1}$,递归边界设为当$x=1,x^{-1}=1$即可在$O(logn)$时间内计算出逆元 # 逆熵 逆熵可以让思路更有逻辑性,否则凭空思考正解完全是在碰运气 ## 二分法 遇到最大值最小 / 最小值最大问题,一般可以用二分答案来处理 ## 排序法 把所有东西(包括数组,边权,甚至询问等)都排序,然后用贪心处理 ## 假设法 假设要求的东西有序,再结合排序法推出第一个线索,然后继续推理 ## 倒推法 先让熵值降低,然后在低熵环境下推理,此后再考虑如何降低熵值 # 暴力 ## 纯模拟 出错几率小,但时间复杂度起飞 不失为狗急跳墙之策 考场上遇到做不来的题目一定要打个暴力骗分,分数比手段更重要 ## 模拟退火 用于处理单峰函数的最值问题 时间复杂度可控,随机次数越多答案越准确 ## 打表 如果答案只跟较少的一些参数有关,那么可以直接用暴力打出一份答案表 必须挂机,需要耐心与信仰 若发现挂机耗时太长,可以分组打表 / 多线程挂机