rt
写在前面
总结一下板子;顺序没有意义;尽量不会压行(但有时候略微压);限于本人水平,有概率会错;有些东西的运用要视情况而定,会给出类似伪代码的东西,还有些之间用文字给出流程
关于编译选项,默认是 -O2 -std=c++14 -Wl,--stack=1145141919
关于头文件,默认是万能头 #include <bits/stdc++.h>
关于宏(包括 using
),默认包含以下宏
1 |
|
大部分情况默认数据范围是 int
大部分情况默认
如未特殊说明,那些写在外面的语句就是主函数里的
由于这些板子学习的时间参差不齐,码风可能会有不同,见谅
数据结构
邻接表
1 | struct Edge{ int ne, ver, w; } e[M]; |
使用前要初始化(如何使用: for (int i = h[x]; ~i; i = e[i].ne)
)
并查集
1 | int get(int x){ return x == fa[x] ? x : fa[x] = get(fa[x]); } //路径压缩 |
堆
STL (大根堆): std::priority_queue<int> q;
手打(小根堆):
1 | namespace Hp //Heap |
左偏树(小根堆):
1 | namespace LH //Left Heap |
树状树组
1 | namespace BIT |
注意下标必须从
ST 表
1 | namespace ST |
四毛子
1 | namespace FR //Four Russians |
线段树
1 | namespace LT |
注意空间开
莫队
1 | 读入 |
莫队二次离线
1 | namespace TOF //Twice Offline |
分块
额……,略
点分治
1 | int get_si(int x, int fa) |
边分治
注意点数要开
1 | int si[N << 1], mxs, rte; |
平衡树
STL1 : std::set<int> s;
STL2 :
1 |
|
Splay :
1 | namespace Splay |
优点是可拓展性强,缺点是慢(被卡的化可以试试多转几下)
FHQ Treap :
1 | namespace FHQ |
补充一个按大小分裂(但是指针码风)
1 | void split(Node *u, int si, Node *&x, Node *&y) |
优点是好打且较快
SBT :
1 | namespace SBT |
优点是快
跳表
1 | namespace SL //Skip List |
CDQ 分治
1 | void cdq(int l, int r) |
这个顺序不是严格的,可以先左右都递归了再计算中间
李超线段树
1 | struct Line |
主席树
普通:
1 | namespace CT //Chairman Tree |
带修:
1 | namespace BIT |
K-D Tree
1 | struct Point{ int x[D], w; }; |
笛卡尔树
1 | void build() |
哈夫曼树
二叉:
1 | 建立小根堆q,把所有点放入 |
多叉:
1 | 用权值为0的点补足点数,使(n - 1) % (k - 1) == 0 |
树链剖分
重链剖分:
1 | void dfs1(int x, int father, int depth) //求每个的重儿子 |
长链剖分:
1 | void dfs1(int x, int fa) |
动态树
以 UOJ207 为例(LCT 维护子树异或和)
1 | struct LCT |
补充,如何用 LCT 支持一些奇怪的操作:
维护子树信息和:思路是维护虚子树信息和;
access
只要在更换右儿子的同时维护一下即可;而在link
的时候,make_root
后把 要access
再splay
一下避免 有祖先信息需要维护维护边权:思路是每个点定义他的“前一条边”和“后一条边”,前一条边指的即是他在原树中与父亲之间的边(可能不存在),而后一条边指的是在当前的链剖分下,与他在同一条剖分链上的儿子与他之间的边(可能不存在),然后每个点维护它的维护他的前一条边和后一条边是哪条,注意是维护是哪条边,而不是直接维护边权
考虑这两两个边的修改:在
link
的时候, 的前一条边要修改;在cut
的时候,一个点的前一条边设成没有,另一个的后一条边设成没有;在access
的时候,我们有更换 的右儿子的操作,即改变了当前的链剖分,所以 的后一条边理应变成他新的右儿子所在的 splay 里最左边的点的前一条边,而为了找到这一条边,我们还需要维护每个 splay 的第一条边和最后一条边,即最左边的点的前一条边和最右边的点的后一条边,(为什么要维护最后一条边呢,因为在翻转的时候我们要交换这两个值);然后我们对每个点维护他的 splay 子树所代表的链的边权信息和(注意一个 splay 所代表的链所包含的边只有那些两个端点都在这个 splay 里的边);那么我们考虑这个信息的合并,如果一个点有左子树,那么他的前一条边的另一个端点一定在左子树里,所以他的链信息和就要加上左子树的链信息和以及他的前一条边的信息,右子树亦然;我们考虑链修改操作,与链信息合并类似,如果他有左儿子,那么更改他的前一条边的权值,右儿子亦然,然后对这个点打标
字符串
KMP
1 | ne[1] = 0; |
下标均从
Z函数
1 | Z[1] = s_len; |
下标从
Trie
普通:
1 | namespace Trie |
Trie 的根是
可持久化:
1 | namespace Trie |
任然是要注意空间
最小表示法
1 | int get_min(char *s) //求串s的最小表示,完成后答案存在s[k...k + len - 1]中 |
注意空间要开两倍
Manacher
1 | int manacher(char *s) |
AC 自动机
1 | namespace AC |
SAM
1 | namespace SAM |
广义 SAM
离线版:
1 | struct Trie |
在线:
1 | struct SAM |
SA
1 | namespace SA |
图论
最近公共祖先
树上倍增法:
1 | void dfs(int x, int fa) |
常数较打,优点是好打且可用于求一个点的
树剖:
1 | 在前面树剖的基础上加入如下代码: |
优点是常数小
ST 表:
1 | int dep[N << 1], cnt = 0, id_b[N << 1], id[N], lg2[N << 1]; //注意2倍 |
优点当然是
也可以用四毛子,但太难打了几乎用不到
单源最短路
Dij :
1 | void dij(int star) |
spfa(慎用):
1 | void spfa(int star) |
判负环
1 | bool spfa(int star) //判断是否有能从star到达的负环 |
差分约束
1 | 建图: |
多源最短路
Floyed :
1 | memset(dis, 0x3f, sizeof dis); |
当然也可以做
最小生成树
Kruskal :
1 | struct Edge{ int from, to, w; } e[M]; |
Prim :
1 | memset(dis, 0x3f, sizeof dis); |
Kruskal 重构树
Kruskal 重构树的建树流程为:
- 把所有边排序,记边为
,表示 ,权为 - 构建
个无权点,没有边 - 每次取出一条边
:- 若
联通(在同一棵树),不管它 - 否则,新建一个节点
,记 所在树的根为 ,让 的左右儿子分别为 ,并让 的点权为
- 若
不难发现 kruskal 重构树有如下性质:
- 它是一棵二叉树(更进一步,它其实就是个二叉堆),树上除叶子结点以外的点都对应着原来生成树中的边,叶子结点就是原来生成树上的节点
- 除叶节点外,儿子节点对应边一定排序在父亲前面,即从每个点到根节点上除叶子结点外按顺序访问到的点的点权是单调的
- 对于叶节点
, 对应的边就是最小生成树中联通 的“瓶颈边”(它排序在最后)
树的直径
dp 自然可以,但不好求出具体的路径
两次 bfs/dfs 的流程如下:
- 任选一个点出发,找到离该点最远的节点
- 从
出发,找到离 最远的节点 -
间的路径就是树的一条直径;如要得到具体路径,在进行 时记录前驱即可
无向图 Tarjan
判桥:
1 | void tarjan(int x, int in_e) |
边双联通分量:
1 | 在判桥的代码后加入以下代码: |
判割点:
1 | void tarjan(int x) |
点双联通分量:
1 | 在上面求割点的代码的(1), (2)处加入以下两段代码: |
有向图 Tarjan
强连通分量:
1 | void tarjan(int x) |
2-SAT
1 | 建图: |
圆方树
直接对原图进行无向图 Tarjan ,因为原图上每个环都是一个点双,而且在栈中的顺序就是环上点的顺序,如果一个点
对于环,我们新建一个方点,对环上每个点连边即可
1 | void tarjan(int x) |
注意新图的节点数变成两倍
三元环计数
要改造一下边,把无向图变成有向图,总时间为
1 | 读入数据,去掉重边,记录每个点的度(下面假设用PII存的边) |
四元环计数
我们将每个点按度数大小重新编号,度数相同原来编号大的点在前的方法,就可以通过编号直接得到任意两个点的关系
设重新编号后排名为
时间复杂度还是 LL
1 | 读入数据,记录每个点的度 |
欧拉回路
1 | void euler() |
二分图
判定:染色法
求最大匹配:
网络流可做,这里介绍匈牙利,时间为
1 | bool dfs(int x) |
一般图最大匹配
智慧法(可能被卡):
1 | std::mt19937 rud(114514); |
带花树:
1 | link同上,get就是并查集 |
Prufer 序列
每次选择一个编号最小的叶结点并删掉它,然后在序列中记录下它连接到的那个结点,重复
虚树
1 | void ins(int x) |
虚树的空间和撤销是易错点
常见转换
- 原图的最大独立集大小 = 补图的最大团大小
二分图:
- 最大独立集大小 = 总点数 - 最小点覆盖大小
- 最小点覆盖大小 = 最大匹配大小
- 最小边覆盖大小 = 总点数 - 最大匹配大小
MCS
1 | namespace MCS |
Bron-Kerbosch
1 | int n, m; |
DP
背包
注意下面初始化都是 0xcf
(最小值)
1 | memset(f, 0xcf, sizeof f), ans = -INF; |
1 | for (int i = 1; i <= m; ++i) f[i] -= f[i - 1] * x; //禁用贡献为x的物品 |
完全:
1 | memset(f, 0xcf, sizeof f), ans = -INF; |
多重背包二进制拆分(以下给出拆分代码):
1 | void split(Node x) |
多重背包单调队列优化:
1 | int calc(int i, int u, int k){ return f[u + k * v[i]] - k * w[i]; } |
单调队列优化
1 | int q[N], l = 1, r = 0; |
各操作顺序不是严格
斜率优化
考虑
1 | int q[N], l = 1, r = 0; |
1 | int find(int i, int k) |
1 | bool cmp(int x, int y){ return X(x) == X(y) ? Y(x) > Y(y) : X(x) < X(y); } |
四边形不等式
式子:
分治打法:
设函数 sol(l, r, L, R)
表示“当然正在处理
1 | sol(l, r, L, R) |
要求
队列 + 二分打法:
1 | q[h = t = 1] = {0, 1, n}; //把0先入队 |
SOS
子集:
1 | for (int i = 0; i < N; ++i) |
超集:
1 | for (int i = 0; i < N; ++i) |
期望
网络流
最大流( Dinic ):
1 | bool bfs() |
最大流( HLPP ):
1 | int hi[N], ex[N], gap[N]; //hi:高度,ex:超额流,gap[i]:高度为i的节点的数量 |
费用流
1 | bool spfa() |
最小割树
把 Dinic 的函数改一下:
1 | void init(int _s, int _t) |
再加上:
1 | namespace GHT //Gomory-Hu Tree |
多项式
拉格朗日插值
一般
1 | void init() |
线性
1 | void init(int n) |
FFT
1 | const int N = 3000000 + 5; //注意N要大于二倍n |
NTT + 全家桶
超长警告
1 | const int P = 998244353, I = 86583718, G = 3; //模数,二次剩余,原根 |
upd 2022/7/23 想改成 std::vector
写发,先放一个 exp
其它后面补
1 | namespace Poly |
MTT
1 |
|
计算几何
浮点数比较
1 | int cmp(DB x, DB y) |
平面最近点对
1 | struct Point |
点间距离
1 | DB dist(Point x, Point y) |
向量
用点表示:
1 | struct Point |
点与线
点到直线距离:
1 | DB dis_z(Point p, Point x, Point y) //p到直线(x->y)的距离 |
点到线段距离:
1 | DB dis_x(Point p, Point x, Point y) |
点在直线上的投影:
1 | DB proj(Point p, Point x, Point y) |
点是否在直线上:
1 | bool is_on(Point p, Point x, Point y) |
线与线
直线交点:
注意点向式表达直线:
1 | Point jiao(Point p, Point u, Point q, Point v) //点项式 |
判断线段是否相交:
1 | bool is_jiao(Point x_1, Point y_1, Point x_2, Point y_2) |
多边形
按逆时针存储所有点
面积:
1 | DB pgarea(Point p[], int n) //polygon area |
判断点是否在多边形内:
射线法,从该点任意做一条和所有边都不平行的射线,交点个数为偶数,则在多边形外,为奇数,则在多边形内
最小圆覆盖
1 | std::mt19937 rud(114514); |
凸包
1 | DB andrew(Point x[]) |
该函数也叫 convex(int x[], int n)
半平面交
1 | struct Line |
旋转卡壳
1 | DB rot_cal(Point x[], int len) |
数论
快速幂
1 | int qpow(int x, int y) |
光速幂
1 | const int BL = (1 << 16) + 5, B = sqrt(P); |
龟速乘
1 | LL mul(LL x, LL y, LL p) |
线性筛
质数:
1 | for (int i = 2; i <= n; ++i) |
莫比乌斯函数:
1 | mu[1] = 1; // 1特判 |
欧拉函数:
1 | phi[1] = 1; |
其它
1 | pri[1] = low[1] = 1; |
杜教筛
式子:
其中
1 |
|
min 25 筛
1 | void prev(int n) |
分解因数/质因数
根号暴力即可
数论分块
1 | for (int l = 1, r; l <= n; l = r + 1) |
欧几里得算法
1 | int gcd(int x, int y){ return y ? gcd(y, x % y) : x; } |
拓展版:
1 | int exgcd(int a, int b, int &x, int &y) |
BSGS
1 | int bsgs(int a, int b, int p) |
拓展版:
1 | int exbsgs(int a, int b, int p) |
中国剩余定理
1 | LL crt(int m[], int a[], int n) |
拓展:
1 | LL excrt() |
逆元递推式
1 | inv[1] = 1; |
斐波拉契数列推导
威尔逊定理
欧拉定理
顺便还有欧拉函数的一个运用:
再给出计算式:
Miller-Rabin 素数测试
1 | const int count = 10; |
调和级数求 的个数
rt,设
1 | for (int i = n; i; --i) |
组合计数
组合数计算
特别的,当
多重排列:
错位排列:
错位排列2 :
圆排:
组合数的一些性质:
做
Lucas 定理
推论:
卡特兰数
莫比乌斯反演
欧拉反演
Dirichlet前缀和
前缀:
1 | for (int i = 1; i <= ct && pri[i] <= n; ++i) |
后缀:
1 | for (int i = 1; i <= ct && pri[i] <= n; ++i) |
逆前缀:
1 | for (int i = ct; i; ++i) |
逆后缀:
1 | for (int i = ct; i; ++i) |
生成函数
普通型 OGF :
指数型 EGF :
群论
置换群
Burnside 引理
其中
Polya 定理
记
矩阵
存储
1 | struct Mtx |
LU分解
1 | namespace DLT //Doolittle |
高斯消元
1 | int guass(Mtx &x, int len) //注意传实参 |
矩阵求逆
1 | bool mtxinv(Mtx &x, Mtx &as, int len) |
行列式
1 | int det(Mtx x) |
调用前要保证是正数
Matrix-Tree
1 | int det(int x[][N]) |
其它
快读快输
1 | namespace FIO |
无法读入浮点数;不可以字符串和整型一起读入(但可以一起输出);注意最后要 flus()
一下;只能文件输入(但可以随意输出),如果要本地输入请 #define gh() getchar()
二分
只会很不优美的写法
1 | int l, r, mid, res; |
三分
1 | int l, r, lm, rm, res, len; |
模拟退火
1 | const DB T0 = 1e4, TE = 1e-4, C = 0.99, SP = 1e3; //这是参数,影响时间和精度 |
取模优化
1 | void adj(int &x){ x += (x >> 31) & P; } |
仅限 LL
时把
Min-Max 容斥
FWT
1 | namespace FWT |
子集卷积
1 |
|
线性基
1 |
|
sqrt
雷神之锤 3 的神仙开根法:
1 | float Q_rsqrt( float number ) |
不知道有啥用
斐波那契数列循环
求斐波那契数列在模
循环节长度上界是
也可以对矩阵做 bsgs ,设转移矩阵为
-
手玩 - 否则,若
是奇质数:- 若
是模 意义下的二次剩余,循环节长度一定是 的约数 - 否则,循环节长度一定是
的约数
- 若
- 否则,
是合数,也有结论,但还不如上两个做法
脚本和指令
win:
1 | g++ %1.cpp -o %1.exe -O2 -std=c++14 -Wall -Wextra -Wl,--stack=1145141919 |
linux:
1 | g++ $1.cpp -o $1 -O2 -std=c++14 -Wall -Wshadow -Wextra -Wunreachable-code -Winline -Wpointer-arith |
高精
没写高精除高精
1 |
|