C++算法竞赛板子大全
1. 并查集
并查集 是一种用于管理元素所属集合的数据结构,实现为一个森林,其中每棵树表示一个集合,树中的节点表示对应集合中的元素。优化方法:
- 按秩合并:秩是当前树的最大高度,按秩合并即将高度小的树合并到高度大的树上,以尽可能压低最终树高。
- 路径压缩:每次查询时,将查询路径上的点的父亲,设置为该树的根节点,以压低树高。
综合使用两种策略,能将
find和union的均摊复杂度压到 **O(1)**。
1 | inline void init_set(int n) //初始化并查集 |
1-1. 种类并查集(实现同上)
种类并查集 是一种特殊的并查集,对于每个属性点,都维护其"反点"。种类并查集可用于判断一系列等于和不等于的关系中,是否存在矛盾。1 | for(int i=1;i<=m;i++){ |
2. 线段树
线段树 可以在 **O(log N)** 的时间复杂度内实现单点修改、区间修改、区间查询(区间求和,求区间最大值,求区间最小值)等操作。但线段树的码量略大,可使用ST表、树状数组作为其简化版本。建树的复杂度为 **O(n)**,小于ST表。线段树将每个长度不为 1 的区间划分成左右两个区间递归求解(可差分),若达到修改或查询的目的区间时,则停止递归并修改或查询该节点,并向上合并修改或答案(可合并)。
线段树使用一个
lazy_tag,来维护上述停止递归时的修改情况。以在下次更深层次的修改或查询时,完成对该节点的子节点的修改。复杂度保证:考虑到一个完整的区间,被递归为左右两个区间的次数不超过
log n。
1 | long long sum[400005]; |
2-1. 加法、乘法、同化求和、求最大值、二分线段树
1 | class SegmentTree |
2-2. 典例:小白逛公园
小白只可以选择第a个和第b个公园之间(包括 a,b 两个公园)选择连续的一些公园玩。且这些公园的分数之和尽可能高。
1 | struct node{ |
2-3. 典例:求区间内 X² 的和
1 | double sum[400005]; |
2-4. 典例:线段树优化DP
将一个长度为 n 的序列分为 k 段,每一段的价值是不同数的个数,使得总价值最大。
1 | void push_up(int now){ |
2-5. 可重集合/奇偶线段树:动态开点、分裂与合并
当
new_flag为 1 时,处理:来回倒腾几颗线段树的有限叶子结点、区间更新时略过空节点的情况;为 0 时处理集合的情况如下:给出一个可重集 a(编号为 1),它支持以下操作:
0 p x y:将可重集 p 中大于等于 x 且小于等于 y 的值移动到一个新的可重集中1 p t:将可重集 t 中的数放入可重集 p,且清空可重集 t2 p x q:在 p 这个可重集中加入 x 个数字 q3 p x y:查询可重集 p 中大于等于 x 且小于等于 y 的值的个数4 p k:查询在 p 这个可重集中第 k 小的数,不存在时输出 -1
1 |
|
2-6. 并行线段树(奇偶线段树)
1 |
|
3. 可持久化线段树(权值线段树、主席树)
可持久化线段树 保留每次"更改"或每次"加入"的状态,即可视为在时空上重合的 n 棵线段树。用于求第任意次修改后的树的状态,求指定区间内的第 k 大的数。1 | //求区间第k大数 |
3-1. 历史版本查询(非权值线段树)
修改一个数组的值,并保留每一个历史版本。每一次查询也会多一个历史版本。
1 | struct node{ |
4. 树状数组
树状数组 是一种支持 **单点修改** 和 **区间查询** 的,代码量小的数据结构,常常用来处理前缀和的修改问题。普通树状数组维护的信息及运算要满足 **结合律** 和 **消去律**,如加法(和)、乘法(积)、异或等。注意:x 不能为 0!!!!!
复杂度保证:我们总能将一段前缀
[1, n]按lowbit拆成不多于log n段区间,使得这log n段区间的信息是已知的,从而实现合并。其中a[k]管辖着lowbit(k)个元素。
1 | class BIT //树状数组 |
求 a[i] 右边,比 a[i] 大的个数:
1 | for(int i=1;i<=n;i++){ |
4-1. 差分树状数组
实现区间修改,单点查询。
1 | void add(int l,int r,int x){ |
4-2. 典例:扫描线(一维)
给出一系列区间,给出一系列线段。扫描线求系列区间包含的线段条数和。
1 | for(int i=1;i<=m;i++){ //b内是查询区间,按右端点从小到大排序;v是线段,右端点非递减排序 |
4-3. 补充:扫描线(二维)
给出一系列二维点,求不包含这些点的最大的矩形的面积。
1 | void f(int x){ |
5. ST表
ST (Sparse Table,稀疏表)是用于解决 **可重复贡献问题** 的数据结构。ST 表基于**倍增**思想,可以做到 `n log n` 预处理,**O(1)** 回答每个询问。但是不支持修改操作。1 | int a[100005]; |
6. 优先队列 + mutable 关键词
1 | //优先队列运算符重载(从小到大排序): |
1. 最长递增子序列最大长度
1-1. 单调数组解法
1 | int choose_2(int ll,int rr){ |
1-2. 树状数组解法
最长上升:
1 | for (int i = 1; i <= n; i++) { |
最长不上升:
1 | for (int i = n; i >= 1; i--) { |
1-3. DP解法(N²)
1 | for (int i = 1; i <= n; i++) { |
2. KMP
时间复杂度为 **O(n+m)**。暴力匹配在随机输入下,复杂度也为 O(n+m)。
1 | class KMP //此代码同时将next数组映射到[1,s2.size()]上,s2为小串 |
3. 字典树
字典树 用边来代表字母,而从根结点到树上某一结点的路径就代表了一个字符串。常用于计算大量字符串中,某个字符串从根节点开始,出现的次数。1 | class Trie |
3-1. AC自动机
AC自动机 (Aho–Corasick)是 **以 Trie 的结构为基础,结合 KMP 的思想** 建立的自动机,用于解决多模式匹配等任务。给你一个文本串 S 和 n 个模式串 T,请你分别求出每个模式串 T 在 S 中出现的次数。1 | struct node{ |
4. 01字典树
在树中找两个结点,求异或值最大的路径。以下处理树上两点间最大的异或和。
1 | int sum[100005]; |
4-1. 可持久化01字典树
在 l 到 r 区间内,找一个数,其与 val 异或能得到的最大值。
1 | int id=0; |
4-2. 典例:可乐
给出数组 a,要求
a[i] xor x ≤ k,构造一个 x,使得满足条件的a[i]数目最多。
1 | void f(){ |
4-3. 典例:最小逆序对
对于数组 a,找到一个 x,获得数组
b[i] = a[i] xor x。使得 b 数组中逆序对数最少,且输出最小的 x。
1 |
|
5. 马拉车求回文串
Manacher算法 用于线性时间求最长回文子串。1 | void proc(){ |
6. k进制哈夫曼树
1 | struct node{ |
1. 正边权最短路 —— Dijkstra(贪心)
复杂度:**O((n+m) log m)**;朴素情况下为 O(m + n²)。
将结点分成两个集合:已确定最短路长度的点集(记为 S 集合)和未确定最短路长度的点集(记为 T 集合)。一开始所有的点都属于 T 集合。
然后重复这些操作:
- 从 T 集合中,选取一个最短路长度最小的结点,移到 S 集合中。
- 对那些刚刚被加入 S 集合的结点的所有出边执行松弛操作。
直到 T 集合为空,算法结束。
1 | struct edge{ |
1-1. 分层图
从 A 到 B,但可以做 k 次”飞机”(没有边权值)。
1 | while(m--){ |
1-2. 典例:佳佳的魔法药水
边权是到某个点的最短路。1 份 A 药水混合 1 份 B 药水就可以得到 1 份 C 药水。最少花多少钱可以配制成功这种珍贵的药水;共有多少种不同的花费最少的方案。
1 | void dij(){ |
调用:
1 | for(int i=0;i<n;i++){ |
1-3. 典例:同余最短路
总高度是 h,有一个电梯,可以向上移动 x 层,向上移动 y 层,向上移动 z 层,或回到第一层。问一共能到达多少层。
1 | for(int i=0;i<x;i++){ |
2. 含负边权最短路 —— SPFA(贪心)
一条最短路中,最多含有 n 个节点。而一个节点每一次入队,就会为当前的队列内的最短路的长度增加 1。
1 | class edge |
2-1. 典例:差分约束
已知不等式组,求任意一组满足这个不等式组的解。
1 | for(int i=1;i<=n;i++){ |
3. 含负边权最短路 —— Floyd(DP)
如果存在一条通过节点 k 的路径,使得从 i 到 j 的距离更短,那么就更新这个距离。
1 | for(int i=1;i<=n;i++){ |
3-1. 典例:传送门
在 l 和 r 之间建立了传送门,0 代价。求更改后的最短路。注意,只能改一条边!!!
1 | for (int l = 1; l <= n; l++) { |
4. 最小生成树 —— Kruskal(贪心)
从最小边权的边开始,按边权从小到大依次加入,如果某次加边产生了环,就扔掉这条边,直到加入了 n-1 条边,即形成了一棵树。
1 | int n, m; |
4-1. 次小生成树
1 |
|
5. 树链剖分
将一棵树,分成若干不重复的链,放入一个数组中。同时,属于同一棵子树的点,在数组中保持连续(dfn序保证)。同时,将每个点的最大的儿子作为其重儿子,在向下经过一条 轻边 时,所在子树的大小至少会除以二。因此,树上的每条路径都可以被拆分成不超过 O(log n) 条重链。
默认情况下,权在点上。否则,需要点边转换。
1 |
|
6. 最近公共祖先 LCA
倍增算法的预处理时间复杂度为
n log n,单次查询时间复杂度为log n。
1 | int ceng[500005]; |
6-1. 图上LCA —— 跑路机
1 | for(int i=1;i<=m;i++){ //从点1到点n,当距离为2次幂时,能在1s内到达。最短需要多少秒? |
7. 树的直径
在一棵树上,从任意节点 y 开始进行一次 DFS,到达的距离其最远的节点 z 必为直径的一端。
1 | int nod=0; |
调用:
1 | dfs(1,0); |
7-1. 树的重心
使得最大的子树最小。重心性质:
- 所有子树的大小小于总大小的一半
- 所有点到重心的距离和最小
- 两棵树合并后,重心在两棵树重心的连线上
1 | void zhongxing(int now,int fa){ |
8. 拓扑排序
在 AOV 网中,顶点表示活动,弧表示活动间的优先关系。AOV 网中不应该出现环,这样就能够找到一个顶点序列,使得每个顶点代表的活动的前驱活动都排在该顶点的前面,这样的序列称为拓扑序列(一个 AOV 网的拓扑序列不是唯一的),由 AOV 网构造拓扑序列的过程称为拓扑排序。
1 | vector<int> G[maxn]; |
9. 割点割边 —— Tarjan
9-1. 无向图-割点
如果 y 是 x 的子节点且
low(y) ≥ dfn(x),那么 x 就是割点。由定义,y 在不经过 (x,y) 的情况下只能到达比 x 更晚访问到的节点,所以删去 (x,y) 后,y 必定与比 x 更早访问到的点不相连,就必然会分裂成一张不连通的子图。
1 | map<int,int> ans; |
调用:
1 | for(int i=1;i<=n;i++){ |
9-2. 无向图-割边
1 | void tarjan(int now,int father){ |
调用:
1 | for(int i=1;i<=n;i++){ |
9-3. 无向图-边双连通分量
无向图的缩点问题,连通分量通过割边连接。一个边双联通分量中,断开任何一条边,都不能破坏分量内部的连通性。
一个点可能属于多个点双,但是一条边属于恰好一个点双。
1 | void taijan(int now,int father){ |
9-4. 无向图-点双连通分量、圆方树
圆方树:圆方交替、度数不低于 2 的圆点,是割点、方点所连的点,属于同一个点双联通分量。
每个点双形成一个”菊花图”,多个”菊花图”通过原图中的割点连接在一起(因为点双的分隔点是割点)。
1 | void tarjan(int now,int fa) { |
调用:
1 | for(int i=1;i<=n;i++) { |
10. 强连通分量 —— KOSARAJU
第一次 DFS,选取任意顶点作为起点,遍历所有未访问过的顶点,并在回溯之前给顶点编号,也就是后序遍历。
第二次 DFS,对于反向后的图,以标号最大的顶点作为起点开始 DFS。这样遍历到的顶点集合就是一个强连通分量。对于所有未访问过的结点,选取标号最大的,重复上述过程。
1 | void dfs1(int d) { |
调用:
1 | suodian(); |
10-1. TARJAN做法
1 | void tarjan(int now) { //有向图不需要father参数!!! |
调用:
1 | for(int i=1;i<=n;i++) { |
11. DFS、BFS搜索
11-1. 折半DFS搜索
1 | void dfs(int now,int sum){ |
11-2. 折半BFS搜索
1 | void bfs(){ |
11-3. 双向BFS搜索
1 | while(!q.empty()){ |
1. 快速幂
1 | long long FastPow(long long a,long long n,const long long p) //求a的n次方模p的值 |
1-1. 数组版
1 | while(m){ |
2. 矩阵加速
以下求
Concatenate(n) MOD m。Concatenate(n)是将 1∼n 所有正整数顺序连接起来得到的数。
1 | struct juzheng{ |
3. 线性递推求逆元
1 | long long inv[maxn]; |
4. 扩展欧几里得
Ax + By = C有解条件:gcd(a, b)整除C。
不定方程的一个特解:x0 = x * C / gcd(a, b)。
对应齐次线性方程组的通解:先求最小非零解a*x1 + b*y1 = 0,得x1 = b/gcd(a,b),y1 = -a/gcd(a,b)。
通解格式:x0 + k*x1,y0 + k*y1。
1 | void exgcd(long long a,long long b,long long &x,long long &y) //求解ax+by=gcd(a,b) |
5. 排列组合数、斯特林数等
组合数(不取模版)
1 | int C(int n,int k){ //不取模版 |
阶乘预处理 + 组合数取模
1 | int jie[10000005]; //阶乘 |
递推求组合数
1 | void buid_C(int n){ //递推求组合数,可取模 |
排列数
1 | int A(int n,int k){ |
斯特林数、错排、卡特兰数
1 | int stirling(int k,int n){ //k球入n盒,使得没有盒为空的方案数 |
5-1. 卢卡斯定理
计算组合数 C,P 为较小质数。
1 | long long p; |
6. 欧拉筛
1 | int vis[maxn]; //它的最小质因数 |
6-1. 埃氏筛
1 | int flag[100005]; |
7. 中国剩余定理
1 | void exgcd(long long a,long long b,long long &x,long long &y){ |
8. 容斥原理
8-1. 典例:硬币购物
对于每次购买,他带了 di 枚价值为 ci 的硬币,想购买 s 的价值的东西,求方案数。
1 | dp[0]=1; |
8-2. 典例:倍数关系
求数组 c 中数(不存在倍数关系)的倍数中,在 [a,b] 内的个数。
1 | void dfs(int now,__int128 sum, int num){ |
8-3. 典例:分特产(ULB问题)
M 种特产分给 N 个人,求方案数。设
g[i]表示刚好有 i 个同学没有土特产,g[0]即为答案。
1 | int flag=-1; |
9. 扩展欧拉定理
1 | void init(int n){ //数组版 |
调用:
1 | p=phi(m); |
9-1. 欧拉定理应用
统计 x,y ≤ n 中有多少对数的 gcd 为质数:
1 | init(n); |
计算 gcd(x,n) 的和,x ≤ n:
1 | for(int i=1;i*i<=n;i++){ |
10. 整除分块
快速找
floor(a/i)相同的区间。
1 | for(int l=1,r=1;l<=n&&r<=n; l=r+1;){//计算 i*flour(k/i),1≤i≤n的和 |
求 x/y 为有限小数的对数。(可分解为 ac/bc,c 不包含 2、5 因子,b 只包含 2、5 因子)
1 | void init(){ |
11. 均值 / PSU 问题
连续的 X 个 1 可以贡献 X³ 的分数,每格有概率 p 成功,求总分数期望。
1 | for(int i=1;i<=n;i++){ |
12. 高斯消元
1 | int line=1; |
12-1. 行列式求值
1 | int flag=1; |
调用:
1 | hanglieshi(); |
13. 异或线性基
1 |
|
13-1. 线性基 + 线段树
求线段树的每个节点都是一个线性基,并对多个线性基进行合并。可以求得一棵树上任意子树的第 K 小。
1 |
|
14. 质因数分解
1 | void get_prim(int tmp){ |
14-1. 约数计数
求 a 的约数个数:
1 | void jishu(){ |
求 a² 的约数个数:
1 | void jishu(){ |
15. 三分
1 | double find(double l,double r) //求l到r单峰函数的极大值点 |
最优子结构 :问题的最优解所包含的子问题的解也是最优的。 无后效性 :当前阶段的求解只与之前阶段有关,而与之后的阶段无关。决策并不是线性的,而需要全面考虑不同情况,分别决策。
步骤:寻找子问题、定义状态、导出状态转移方程、确定边界条件。
0. 纸币问题
1 | void dp(){ |
1. 字符串 DP
求能由一些小串组成的大串的最大长度:
1 | dp[0]=1; |
两个字符串的最少编辑距离(增、删、改):
1 | void dp() { |
从字符串 A 中,选取 k 个块拼成 B 的方案数:
1 | dp[0][0][0][0]=1; |
2. 背包 DP
2-1. 典例:聪明的奶牛
1 | for(int i=0;i<=800000;i++){ //智力和大于0,情商也要大于0,两者和最大多少 |
2-2. 分组背包 DP
1 | for(int i=1;i<=n;i++) |
2-3. 朴素 01 背包
1 | f = vector<int>(V + 1, 0); //V可以不充分利用!! |
3. 区间 DP
3-1. 典例:祖玛
不断移除回文子串,最多需要几次:
1 | for(int i=1;i<=n;i++){ |
3-2. 典例:大爷关灯
有一个大爷在 c 点,左右去关灯,关灯前持续消耗功率:
1 | dp[c][c][0]=dp[c][c][1]=0; |
3-3. 典例:手串(矩阵优化)
环状图,任意 m 个连续的地方,不得超过 k 个 1:
1 | cin>>n>>m>>k; |
3-4. 典例:合数
相邻两相同数合成大 1 数,利用倍增思想:
1 | for(int i=1;i<=n;i++){ |
4. 树上 DP
4-1. 树上背包 DP
现在这颗树枝条太多了,需要剪枝。但是一些树枝上长有苹果。给定需要保留的树枝数量,求出最多能留住多少苹果:
1 | for(int i=0;i<son[now].size();i++){ |
4-2. 树上路径 DP
求树上值最大的路径(边权含负值):
1 | void dfs(int now,int fa){ |
4-3. 换根 DP
1 | void dfs(int now,int father){ |
5. 状压 DP
5-1. 典例:吃奶酪
房间里放着 n 块奶酪。一只小老鼠要把它们都吃掉,问至少要跑多少距离?
1 | for(int i=1;i<=n;i++){ |
5-2. 典例:炸鱼
炸弹可以十字形爆开,炸掉所有格子的鱼,最少要多少个炸弹?
1 | for(int i=originstate;i>=0;i--){ //初始状态压每个格子的鱼数目(0,1,2,3) |
6. 其他 DP
6-1. 典例:道路游戏
同一时间最多存在一个机器人(机器人初始位置不同),最多走 k 步。硬币随时间分布,求 m 秒最多收益:
1 | for(int i=1;i<=n;i++) cin>>c[i];//买机器人的费用 |
6-2. 典例:买股票(单调队列优化)
第 i 天的股票买入价为每股 APi,第 i 天的股票卖出价为每股 BPi(数据保证对于每个 i,都有 APi≥BPi),但是每天不能无限制地交易,于是股票交易所规定第 i 天的一次买入至多只能购买 ASi 股,一次卖出至多只能卖出 BSi 股。两次交易最少相隔 w 天。
1 | for(int j=0;j<=a[i].as;j++){ |
6-3. 典例:午饭
N 行 m 列,值为 a[i][j],第 i 行最多选值 1,求存在一列的选数和 > 总和一半的方案数:
1 | for(int w=1;w<=m;w++) {//枚举过半的那列 |
6-4. 典例:游戏
n 个格子有 k 个连续块的方案数,n、k 为 1e5:
1 | int main(){ |
6-5. 典例:拆分
给定一个整数 n,求将 n 分解为互不相同的不小于 2 的整数的乘积的方案数:
1 | for(int i=1;i<=n/i;i++){ |
6-6. 典例:高维 DP - 组牌
牌从 1 到 n,可以出连续五个牌的顺子、2 或 3 个的对子。最小化 3-n 间余下的牌:
1 | int proc() { |
1. 关闭同步流 + 文件读入输出
1 | ios::sync_with_stdio(false); |
2. 快读
1 | inline long long input(){ |
3. 数列离散化
1 | int a[maxn],d[maxn]; |
3-1. 二维离散化
OVERPLANTING 问题——坐标系内有多个可重叠矩形,求其面积之和:
1 | for(int i=1;i<=n;i++){ |
4. 反悔贪心
T1 为抢修时间,t2 为截止时间。求最多维修量(按 t2 排序):
1 | for(int i=1;i<=n;i++){ |
5. 双指针
一次需要包含 num 个不同的 id,求最小的距离差:
1 | for(int i=1,j=1;i<=n;i++){ |
求离自己第 k 近的数:
1 | f[1]=k+1; |
6. 根号分治
给你一个长度为 5×1e5 的序列,初值为 0,你要完成 q 次操作:
1 x y: 将下标为 x 的位置的值加上 y。2 x y: 询问所有下标模 x 的结果为 y 的位置的值之和。
1 |
|
7. 单调队列
求长度为 k 个数的最大值,建立非递增队列:
1 | for(int i=1;i<=n;i++) { |
8. 单调栈
求每个数后面第一个大于自己的数;建立非递增栈:
1 | for(int i=1;i<=n+1;i++){ |
9. 迭代器与容器操作
1 | //map迭代器 |
10. bitset 操作
1 | std::bitset<1000> bs; //声明 |
11. 对拍器
数据生成代码 data.cpp
1 |
|
暴力代码 baoli.cpp
1 |
|
正解代码 sol.cpp
1 |
|
对拍代码(Windows)
1 |
|
对拍代码(Linux)
1 |
|
12. 莫队算法
莫队算法 :离线处理询问,可以 O(1) 时间完成一个区间到相邻区间的转移。时间复杂度 O(n√m)。1 | int BLOCK_SIZE; |
13. 调参器
13-1. 评测器
1 |
|
13-2. 调参器
1 |
|
