ACM笔记
开头
刷题的作用
- 知识点
- 思维
- 代码调试和实现
- 前期:知识点 > 思维, 代码实现 > 代码调试
训练方案
- 提高难度,借助题解刷题
知识
- 有体系(自底向上)
- 无体系(归纳法)
数学
异或
- CF2225D 前缀异或和
异或线性基
给出一个数列 a1,a2,…,an,若一个序列 d1,d2,…,dk 满足以下性质:
- 所有原数列中的元素能够异或出来的值 d 中的数也能异或出来
d[i]中非零的个数就是这个线性空间的秩(线性无关的向量的个数)
- P3812 最大值问题 从高位到低位贪心异或
- 最小值问题
- hdu3949第k小问题: 净化后的p数组, 二进制拆分
- P3857异或和问题
- P4570 贪心插入元素,动态查看能否被消掉 (极大线性无关组的个数是常数(秩))
鸠巢原理
有N + 1个可能, 但是只有N个抽屉, 那么必然至少有一个会有重复的
- 200_D
质数筛
任何一个合数都是可以整除比他小的一个质数
- P5440 要判断一个数是否是质数, 就要看的范围里的质数能不能整除
- 412_E 区间筛法, 用小筛子去筛大数, 对于每个小质数, 乘以 倍数后把[L,R]里的合数筛掉,剩下的就一定是质数
矩阵加速
-
斐波那契 POJ-3070
-
POJ-3233
- P1306 斐波那契数列最大公因数性质
斐蜀定理
两个互质的数a和b
凑不出的最大整数为, 在S之后的数都可以用a 和 b凑出来(用于路径压缩), 所以大于S的路径直接变成S长度
- 路径压缩 P1052
概率论
-
逆元的计算(费马小定理( 前提是p是质数), 扩展欧拉函数): 适用于求单点的逆元
-
逆元的预处理 (阶乘预处理, 再处理逆元) : 适用于大范围的逆元
-
模类Z
-
组合数
计算几何
扫描线
动态规划
定义dp数组 -> 初始化dp数组 -> 转移方程
DP数组的初始化
- 求方案数(初始化为-1)来看看是否算过
- 求最小值(初始化为1E9)
- 求最大值(初始化为-1E9)
线性DP
dp数组的思考 : 有足够的信息, 不会破坏无后效性
- 二维dp P2758
- 带状态的dp P1541乌龟棋
- abc_418D
最长递增子序列
tails数组 --> tails[1] = 7 代表长度为2的LIS的最小结尾数字为7
- P1020 找
<=序列用upper_bound(>), 找<序列用lower_bound(>=)
树上DP
套路: 父节点的全集信息需要靠子节点的全集信息递归来返回
-
例题
数位DP
利用条件判断加递归
-
技巧
- 利用转字符串来的到数字的某一位
-
例题
- P2602
状压DP
dp[status][i]status == (1 << MAXN), 用位来表示每一个东西已经选了或者还没选(状态的子集状态)
- 例题
- 旅行商问题 P1433 P1171 列举mask从小到大
- 棋盘问题 P1896 P2704
- 老鼠吃奶酪 P1433
区间DP
小区间合并成大区间
一般允许的复杂度
-
[数字游戏]:(http://10.160.111.129/p/2873
-
P1775 石子合并
0-1背包
两个变量: 容量和价值, 依次遍历每个物品,更新dp[容量] = std::max(价值1, 价值2), dp[i]数组定义是在容量为x的时候最大价值是多少, 可以买或者不买来更新
- 410_E
- P1776 0-n背包(需要打包操作, 要从小到大的打包)
数据结构
链表
- P1996
- P1160
带版本控制的链表(可持久化)
- 411_D
前缀和 差分
二维前缀和公式 :
a[i][j] = a[i][j] + a[i - 1][j] + a[i][j - 1] - a[i - 1][j - 1]二维差分公式:
d[x1][y1]++, d[x1][y2 + 1]--, d[x2 + 1][y1]--, d[x2 + 1][y2 + 1]++
- 410E 二维前缀和
- abc_419D 差分数组
BIT
a[0011] 代表以3结尾,长度为1的区间和, a[0100]代表以4结尾,长度为4的区间和
维护可差分(可减性)信息(由两个大区间得到一个小区间)
-
单点修改 + 区间查询(维护原数组) P3374
- 求逆序对 P1774
- 树上逆序对 P3605
- 离线查询 + 维护区间出现了几次 P1972 P4113
-
单点查询 + 区间修改(维护差分数组)P3368
-
区间查询 + 区间修改 (维护D和Di)P3372
-
二维数组 + 单点修改 + 区间查询 (维护二维数组)
-
二维数组 + 区间修改 + 区间查询 (维护二维差分数组)P4514
DSU
- 408_E :求两点之间最小OR路径, 维护连通性
- Bailian-1182 **P1525 **拓展域并查集
- hdu3038 hdu3635带权并查集, 相对位置…
- P4185 并查集加离线海量查询
- P2294 带权并查集
RMQ
对数组的范围静态查询, (最大值, 最小值, 最大公约数)
可以理解成管辖[i, i + (1 << k))的一个值
- 倍增思想: 就是很大的区间分成一个较大的区间,然后剩下的区间再递归… P4155
线段树
区间查询 + 区间修改 + 一维数组 + (有组合律)
- P3372 区间和
- P13825 动态开点线段树
单调栈
找到阶段性的强者
- 例题
- 动态后缀最大值 P1198
- 求全0矩形的方案 P1950
- 求最大全0矩形 P4147
单调队列/滑动窗口
与普通的队列不同的是,这是一个双端队列
- P1886 求一个窗口内的的最大值, 最小值
- P1714 求不定长子段最大和(前缀和 + 滑动窗口)
- P3957 二分答案 + [L, R]的单调队列 + 坐标离散
- P1419 二分答案 + [L, R]的单调队列 + 前缀和
堆/优先队列
自动维护最小值,最大值, 反悔贪心的关键数据结构
LCA(最近公共父节点)
作用:
- 求树上两点的距离
- 树上差分(点差分[diff[u]++,diff[v]++,diff[lca]—, diff[fa[lca]]]—,边差分 diff[u]++,diff[v]++,diff[lca]-2)
- P3379 模板
- P2680 , P9246边差分
算法
搜索
每个节点都是状态(位置, 颜色…), 我们需要找到一条从起点到终点的有效边
-
暴力搜索 -> 剪枝叶(处理无效状态)
- P5294
- P1118 一维上的, 从左到右选数问题
-
暴力搜索 -> 开dp数组(处理重复问题)
-
折半搜索(用空间换时间), 先把前半部分的结果存起来(DFS)
-
适用于一维上的选和不选求子集和, 然后
-
时间:
-
空间:
-
P3067 折半搜索 + 掩码(表示集合状态)+ vector排序去重替代set
-
P4799
-
P5195 两段BFS
-
-
IDA*(带有深度限制的, 带有估价函数剪枝的DFS)
-
适用范围是: 状态的分支太多会爆队列, 却要求最小步数
-
POJ3134
-
分治法
把大问题分成两半, 再递归合并
- P1908 分治求逆序对
- P3806 点分治
CDQ分治
倍增
- P3509 滑动窗口 + 倍增跳跃
图论
DFS
父节点给子节点初始化信息, 先操作,后递归
子节点给父节点更新消息,先递归, 后操作
- 417E 派出一个哨兵来给我信息这条路可能通终点吗
BFS
-
多源BFS ABC_405_D
洪水填充
- P1162
- P1506
二分图
-
-
二分图最大匹配(匈牙利算法): 二分图中每条边都是一对一的(没有公共节点)P3386
哈夫曼树
高频短码, 低频长码, 从底至上
- P1090
- P2168 K分支哈夫曼树, 要挂空节点保证最后剩一个
dji算法
优先队列维护的是全局的最近的点
算法复杂度 N+M是遍历了图中的所有点和边, logN是优先队列比较每个点
- P3956 带颜色状态的Dij算法
- P1126 机器人搬货物,要判断合法状态
- [L2-001](https://pintia.cn/problem-sets/994805046380707840/exam/problems/type/7?problemSetProblemId=994805073643683840&page=1)dji模板加路上的状态维护
- P2865 求次短路径(维护dist1和dist2, 一个最短一个次短)—> 拓展K短路(暂时不学)
- P1266 分层图dij, 带一个速度状态的dij
- P5304 多源dij + 染色法, 用于求某个点距离该集合最近的距离和点
floyd
求全局任意两点的最短路,
剪枝很重要, 很有可能不剪枝过不了hdu_1704
// abc_416_E 动态增加新路 O(n^2)
dist[i][j] = std::min({dist[i][j],
dist[i][x] + dist[x][y] + dist[y][j],
dist[i][y] + dist[y][x] + dist[x][j]});
- hdu_1385 floyd维护最短路径(1->2->3…),利用
nxt[i][j]数组, 拓展: nxt[i]可以用于dijiskra算法的单源最短路径 - P1119 按照时间顺序来写中转点来更新dist
- P1913 floyd前要先倍增建图
- hdu_1704 把
dist[i][j]想成i和j的关系, 他们之间可以通过中转点K来确定另一个关系,统计答案的时候不要重复统计 - P1730 初始化floyd的时候多加一个维度
f[i][j][k]表示i到j经过了k条边的最大路径和
spfa
用来判断图中是否有负环,初始的时候要把所有点入队(防止图不连通), 所有dist都为0
如果没有负环, 起始节点到目标节点的走过的边数应该是<= V - 1(整个图的总点数)
如果是从0号点出发,则其他的dist应该为INF(这种是求最短路顺便判断负环)
算法复杂度: 最坏是 菊花图
- P5960 差分约束,判断负环存在
- P2868 二分计算边的权重,spfa判负环
- P3385 单点可到达的负环
- P2294 差分约束
johnson
求全源最短路
先用spfa求势能,再跑n个dij算法
- P5905
tarjan
SCC: 强连通分量, 一个有向子图(不是树)中任意两个点连通
: dfs顺序遍历的编号
: u为根的子树的所有节点能够到达的最小dfn序
割边: 如果删掉这条边, 图中的连通块(分量)变多
割点: 如果删掉这个点, 图中的连通块(分量)变多
- 割边 P1656
- 割点 P3388
字符串
马拉车
解决回文问题
KMP
找到S1中的S2的位置 ,
数组是在[0, i)的前缀中的最长公共前后缀
则 则x为最小循环字串的大小
字符串哈希
判断字符串是否相等
字典树(前缀树)
记录多个字符串的前缀
- 维护字符串的字典树
- 0-1 Trie 求路径最大异或和 P4551
杂项
CF思考角度
从局部思考推到整体
- CF2225B 把l和r影响的是局部和其他, 内部不影响, 外部最后影响两个冲突
旋转操作
- 顺时针90°
- 逆时针90°
// 顺时针
vector<string> ns(n);
for (int i = N - 1; i >= 0; --i) {
for (int j = 0; j < N; ++j) {
ns[j] += s[i][j];
}
}
// 从最后一行向上从左到右发牌
// 1 2 3 7 4 1
// 4 5 6 ====> 8 5 2
// 7 8 9 9 6 3
// 逆时针
vector<string> ns(n);
for (int i = 0; i < N; ++i) {
for (int j = 0; j < N; ++j) {
ns[N - 1 - j] += s[i][j];
}
}
//
// 1 2 3 3 6 9
// 4 5 6 ===> 2 5 8
// 7 8 9 1 4 7
位运算
// 取一位数n的最高位1的位置
int high_bit = 63 - __builtin_clzll(n);
// 制作一个数前i位都是1 (11111100000)
-(1 << i) + 1;
// 取一个数的最小的一位1
x & -x
// 取x的第i位
(x >> i) & 1
// 取反x的第i位
x ^= (1 << i)
// 将x的第i位取0
x &= ~(1 << i)
- 408E 通过贪心求最小或和
贪心
反悔贪心
在一维数组上进行要或者不要(这个值), 带有前缀条件,用优先队列
先无脑入队列
- 例题
- 407_e
- **P30
最大子段和
Kadane 算法 : 通过维护历史最值来判断是继续加,还是清零
- 普通的Kadane算法P1719
- 环形Kadane算法**P1121*
破环成链
- P2629
- P4155 断环成链后, 寻找覆盖区间超过环的连续区间就OK了
如何调代码
#include <bits/stdc++.h>
#define _CLR_CYAN "\033[1;36m"
#define _CLR_GREEN "\033[1;32m"
#define _CLR_RESET "\033[0m"
using i64 = long long;
using i128 = __int128;
inline std::ostream& operator<<(std::ostream& os, __int128 n) {
if (n == 0) return os << "0";
if (n < 0) { os << "-"; n = -n; }
std::string s;
while (n > 0) { s += (char)('0' + (n % 10)); n /= 10; }
std::reverse(s.begin(), s.end());
return os << s;
}
template <typename T>
void dbg_out(const T& val) { std::cerr << val; }
template <typename A, typename B>
void dbg_out(const std::pair<A, B>& p) { std::cerr << '(' << p.first << ", " << p.second << ')'; }
template <typename T>
requires requires(T t) { t.begin(); t.end(); }
void dbg_out(const T& v) {
std::cerr << '[';
bool first = true;
for (const auto& x : v) {
if (!first) std::cerr << ",";
first = false;
dbg_out(x);
}
std::cerr << ']';
std::cerr << "Total:" << v.size() << '\n';
}
template <typename T, std::size_t N>
void dbg_out(const T (&a)[N]) {
std::cerr << '[';
for (std::size_t i = 0; i < N; ++i) {
if (i > 0) std::cerr << ",";
dbg_out(a[i]);
}
std::cerr << "]";
std::cerr << "Total:" << N << '\n';
}
template <typename T>
requires requires(T t) { t.begin(); t.end(); }
void dbg_out_range(const T& v, int L, int R) {
std::cerr << '[';
bool first = true;
auto it = std::next(v.begin(), L);
for (int i = L; i < R && it != v.end(); ++i, ++it) {
if (!first) std::cerr << ",";
first = false;
dbg_out(*it);
}
std::cerr << "]";
std::cerr << "Range: [" << L << ", " << R << "), Total: " << v.size() << '\n';
}
template <typename T, std::size_t N>
void dbg_out_range(const T (&a)[N], int L, int R) {
std::cerr << '[';
for (int i = L; i < R && i < (int)N; ++i) {
if (i > L) std::cerr << ",";
dbg_out(a[i]);
}
std::cerr << "]";
std::cerr << "Range: [" << L << ", " << R << "), Total: " << N << "\n";
}
#define debug(x) \
std::cerr << _CLR_CYAN << "[" << #x << "]" << _CLR_RESET << '\n'; \
std::cerr << _CLR_GREEN; dbg_out(x); std::cerr << _CLR_RESET << '\n';
#define debug_range(x, L, R) \
std::cerr << _CLR_CYAN << "[" << #x << "]" << _CLR_RESET << '\n'; \
std::cerr << _CLR_GREEN; dbg_out_range(x, L, R); std::cerr << _CLR_RESET << '\n';
- int 与 int 相乘 会溢出, 应该转为i64
字符串的stl操作
string text;
text.find(word, pos) // ==> string::npos;
text.replace(pos, len, str)
// 输入
std::cin // 会留下一个换行符, 忽略空格换行符
std::cin.ignore(); // 吃掉一个字符
std::cin.get(c)
std::getline(std::cin, s); // 读取,直到换行符,会吃掉换行符
// 标准流程(读取多行字符串)是
int n;
std::cin >> n;
// 坚决清空第一行数字后面的所有残留物
std::string dummy;
std::getline(std::cin, dummy);
for (int i = 0; i < n; ++i) {
std::string s;
std::getline(std::cin, s); // 干净清爽地读取每一行
// 处理字符串 s ...
}
天梯赛的阴函数
// 补全前导零
os << std::setfill('0') // 全局有效i
os << std::setw(5) // 只对后面一个数有效
sort的元素满足传递性
- P2123 中我们可以通过sgn(x) 来比较 ,
较难知识点
- ABC_454G —> DSU on tree