alogrithm
ALOGRITHM
本文章是 基于 左神课程 + 牛客课程 进行算法系统式学习的模版,用于比赛时候的板子。
任务安排(📑) | 完成情况(✅) | |
---|---|---|
线性基 136 + 137 + 高斯消元 | ||
基础算法汇总 |
基础算法
单调队列
[!NOTE]
这是一次尝试
单调栈
逆序对
- 树状数组
使用树状数组来求逆序队,主要要进行离散化,应为可以 a[i] 的值很大,但是 数组大小有限,不离散化的话,可能树状数组的 tree数组存不下。
思路:
从数组的右边开始便利,访问到当前元素的时候,检查树状数组中求和(比当前元素小的元素),因为是从右往左便利,如果有比当前元素小的,肯定在数组中是在当前元素的右边。
1 |
|
归并分治
分治的含义是 整体的答案 $?=$ 左边的答案 + 右边的答案
- 归并排序
归并排序是一个稳定的排序
- 整体的有序 是 左边有序 + 右边有序 + 合并过程
1 |
|
分治题目:
leetcode-分治
随机快排
$$
$$
离散化
如果数据规模大,但是数据量小的话,我们可以进行离散化处理。
- 给每一个值一个编号
- 法一
1 | vector<int> b(n); |
类并查集
如果是分联通块进行计算,我们可以不使用并查集,使用一个简单的思路,给每一个联通块编号,从而来考虑每一个联通块之间的关系。
-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
using namespace std;
void __(){
int n, m1, m2;
cin >> n >> m1 >> m2;
vector<vector<int>> a1(n + 1);
vector<vector<int>> a2(n + 1);
for(int i = 0; i < m1; i++){
int u, v;
cin >> u >> v;
a1[u].push_back(v);
a1[v].push_back(u);
}
for(int i = 0; i < m2; i++){
int u, v;
cin >> u >> v;
a2[u].push_back(v);
a2[v].push_back(u);
}
// 进行编号
vector<int> col1(n + 1,0);
vector<int> col2(n + 1, 0);
auto dfs2 = [&](auto f,int u, int k) -> void{
col2[u] = k;
for(auto x : a2[u]){
if(col2[x] == 0){
f(f,x,k);
}
}
};
auto dfs1 = [&](auto f, int u, int k) -> int{
int cnt = 0;
col1[u] = k;
for(auto x : a1[u]){
if(col1[x] == 0){
if(col2[x] != k) cnt++;
else cnt += f(f,x,k);}
}
return cnt;
};
int ans = 0;
for(int i = 1; i <= n; i++){
if(col2[i] == 0){
dfs2(dfs2,i,i);
}
if(col1[i] == 0){
ans += dfs1(dfs1,i,col2[i]);
if(col2[i] < i) ans ++; // 在前面有代表节点的时候便已经访问过这个节点,但是在F图中这个 i 节点并没有被 初始化,说明前面的代表节点和这个节点之间应该要增加一条边
}
}
cout << ans << endl;
}
signed main(){
int _;
cin >> _;
while(_--) __();
}
快速幂
无 mod
1 | int fpw(int a, int b){ |
有mod
1 | int fpw(int a, int b, int mod){ |
乘法逆元
线性
1 | inv[1] = 1; |
扩展欧几里得
费马小
$$
ax \equiv 1(mod ; b) \
x = a^{b - 2}(mod ; b)
$$
1 | x = fpw(a, b-2,b) |
数据结构
折半搜索
并查集
基础并查集
1 | int father[N]; |
带权并查集
个人感觉带权并查集有点类似于 树形DP,后续补充
‘hello’
前缀和 + 差分
前缀和
一维
$$
sum[i] = sum[i - 1] + a[i]
$$
二维
$$
sum[i][j] = a[i][j] + sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1]
$$
树上前缀和
- 点权
$$
x\rightarrow y 的路径 = sum_x + sum_y - sum_{lca} - sum_{f_{alca}}
$$
- 边权
$$
x \rightarrow y 的路径 = sum_x + sum_y - 2 * sum_{lca}
$$
差分
用于快速解决区间修改问题
一维
$$
b_i = a_i - a_{i - 1}
$$
如果存在 [l, r] 区间内的值进行范围修改,在差分数组上面 $b_l + d,b_{r + 1} - d$
二维
$$
diff[i][j] = a[i][j] - a[i - 1][j] - a[i][j - 1] + a[i - 1][j - 1]
$$
$$
a[i][j] = \sum_{t1 = 1}^{i}\sum_{t2 = 1}^{j} diff[t1][t2]
$$
树上差分
- 点差分
如查询一棵树上 节点被访问的次数
Ex: s 到 t 路径节点的访问修改
$$
d_s = d_s + 1 \
d_{lca} = d_{lca} - 1\
d_t = d_t + 1\
d_{f(lca)} = d_{f(lca)} - 1
$$
当前节点的权重,由于当前节点的权重与子节点有关,便可以很好的解释 上面这个差分访问修改
$$
a[i] = \sum^{i的子节点}diff[t]
$$
- 边差分
Ex: s 到 t 路径的边的访问修改
$$
d_s = d_s + 1\
d_t = d_t + 1\
d_{lca} = d_{lca} - 1
$$
当前节点的权重,由于当前节点的权重与子节点有关,便可以很好的解释 上面这个差分访问修改
$$
a[i] = \sum^{i的子节点}diff[t]
$$
线段树
基础线段树
懒更新的意义是,如果这个大区间进行了修改,当访问到这个大区间的子区间时我们再去下发大区间的修改信息。
区间查询,区间变化,区间重置
1 |
|
区间重置 + 范围查询
1 |
|
范围修改 + 范围查询
1 |
|
线段树的势能分析
线段树区间合并
处理 子串 子数组(相互连接) 的信息
1 |
|
开点线段树
- 问题
- 支持很大的范围,但是查询次数少,查询区间小
时间复杂度 2 * m * log(n),用 cnt 来记录编号,看节点分支是否访问过。
线段树历史最值操作
标签回收 → 进行剪枝
图论
最短路问题
遇见过很多 最短路 问题,题目的意思一般都指向明确,就是(i $\rightarrow$ j)的路径最小。
[!NOTE]
但是一般都是 最短路的扩展, ex:到达终点时候需要满足什么样的状态
题目链接:
- 扩展迷宫问题
个人认为这道题非常的典,题中还应用了一类 自定义点,来管理相同类别的点之间的跳转。
1 |
|
拓扑排序
适用于那些 节点状态有先后性的问题。
- 如果需要求 最大/最小的拓扑排序的话,可以将存储的 deque 改成 优先队列
1 | signed main(){ |
- 板子题:P1347
负环 差分约束
形式
- $$
x_i - x_j \leq C_i \quad \text{($C_i$ 为常数)}\
找到一组满足这种式子的解
$$
根据 小于号 的不等式来建图
$$
x_i \leq x_j + C_i
$$
将这个式子应用到 图中,如果图中存在负环就不存在满足的解。
- 负环判断: 如果这个节点进队列 $\geq$ 所有节点的 个数 - 1
- 如果队列为空,dist[n] 便是一组解
数学
基础数论
- 任何一个整数 都可以分解为 有限个质数的乘积
$$
N = p_1^{c_1} * p_2^{c_2} \dots p_x^{c_x}
$$
则 N 的约数集合为
$$
约数集合 = {p_1^{b_1} * p_2 ^ {b_2} \dots p_x^{b_x}} \
0\le b_i \le c_i
$$
正约数的个数 :$\prod_{i=1}^{m}{(c_i +1)}$
- 质数筛的算法只需要 $log{(n)}$
高斯消元
高斯消元 有三种类别,我们会逐一补充,目前先补充 异或消元,但是其实本质是不变的。
线性基
给出一个数组,请找出这个 数组的任意元素进行异或,可以得到多少种情况。
线性基的大小便决定了 能异或的多少种情况 (2 ^ n - 1)
1 |
|
- 高斯消元
1 |
|
常见考点:
求异或最大值
从 最高位开始枚举,来进行更新最大值
1 |
|
求 异或 第 k 小值
1 |
|
扩展欧几里得
中国剩余定理
由扩展欧几里得算法(后续填完扩展欧几里得会补充)
动态规划
区间DP
当实现 区间DP 的时候,如何将递归该迭代
- 首先枚举每一个长度的区间,然后在枚举每一个区间的开头(这样可以避免判断边界条件),然后判断这个区间的扩展,因为当你枚举到这个长度的区间的时候,你是一定枚举完这个区间的子区间的。
- 注意 子区间中存在一些特判的情况!
计算几何
博弈论
杂项
数据
通过数据量的给出,可以大概的估计是一个什么时间复杂度的算法,从而来推演这个算法的类型。
或( | ) 和 与( & )
对于或和与的一些讨论
- 或 是只要有一个 1,或值 中这个位置就一定会存在一个 1
可以考虑 这个位置的 一是否需要存在来进行按位判断
- 与 是需要全部都是1,与值 中这个位置就一定存在 1
最大公约数 和 最小公倍数 的关系
lcm(a, b) * gcd(a, b) = a * b
a * b = 质数
→ (a = 1, b 为 质数
)
- 在进行 GCD 的过程中,随着 不断进行 GCD,GCD的值只可能不变或者变小
异或值
- 遇到异或值可以考虑 枚举每一个位置,看是不是每一个位置的变化会对答案有影响(规律性)
二进制
- 在二进制的位移中 1是会被默认当作 (int)类型的
需要 (1ll << j) 防止溢出
如 (1 << j) 会被默认为 int 类型
图的创新知识
- 存在多个 起点的时候,可以设置虚点。
- 遇到中位数(对顶堆)
- 树上的 两个节点之间 只存在一条最短路径
二分
- 二分是 x 值成立 $\leq or \geq$ x 的值都成立
- 二分具有单调性:
- 当 x 成立的 时候, 小于 x 的值都是一定成立的。
对于数组中 不同位置的值 能否相互抵消,变成全是 0 的数组
如果当前 元素的个数大于了 数组的和的一半肯定是不成立的
区间异或问题
对于一个区间 [l, r] 来说, l ^ r
中的 最高位的 1,相当于 最高位后面的 1都可以由 [l, r] 中的值异或得到。
1
在计算机中会自动被默认设置为 int 型,所以如果有long long 的话,需要 改变 1 的类型, (long long)
矩阵问题
矩阵 行列异或
- 通用思考:找一下 总的异或 的值的关系
- 尝试去构造矩阵