本文档总结了非ACMer应知应会基础算法cpp版本,不涉及算法详解,适合(保研机考/找实习)复习框架使用。
大部分资料来源与互联网各大佬的总结与整理,此处作为复习整合。此外非常感谢学长提供的算法模板手册!
施工建设中……
0 program basic
以下是c++
语言基础部分参考复习
0.1 万能头
1 |
0.2 输入输出处理
cin/cout
getline(cin, str, '\n')
:默认读入一行,可以更换截断字符
读取不定行数:while(getline(...))
0.2.1 输出保留小数位数
1 | double a=46.21534,b=1.20001; |
0.2.2 int string互转
【int->string】
to_string(int)
ostringstream
:1
2
3ostringstream ss;
ss << num;
cout << ss.str();
【string->int】
istringstream
atoi(str)
0.2.3 string操作
1 | string s; |
0.3 std::sort踩坑记录
在使用排序的过程中我们通常为了省时省力会选择使用c++库中提供的sort函数,重载compare来进行。众所周知sort底层原理是快速排序,但是实际上的情况不尽如此。
在使用sort排序一个大数组时,我碰到了如下问题:
AddressSanitizer: heap-buffer-overflow on address 0x7f6fbd3a47e8
网上查阅相关资料得知,原来是因为compare重载时,两个数出现==
的情况也返回了true,这就导致出现循环相等的情况。因此,在大数组排序时,出现相等元素一定不能cmp函数返回true。
0.4 文件输入输出
1 |
|
0.5 java
从以下部分开始是java的基础处理部分
0.5.1 输入输出
使用scanner可以方便处理命令行输入
1 | Scanner sc = new Scanner(System.in); |
使用System.out.print
或者System.out.println
可以输出,输出格式规则和c语言printf
相同。
0.5.2 文件
File
类相关的操作
1 | File file = new File("path"); |
0.5.2.1 读取文件
1 | // 使用Scanner |
0.5.2.2 写入文件
1 | // 使用PrintWriter |
以上方法都会重写原文件,即覆盖原文件
【追加写入】
1 | File file = new File("path"); |
0.5.2.3 junit相关文件比较
0.5.3 string操作
0.5.3.1 string和int互转
string -> int:
1 | String s; |
int -> string:
1 | int a; |
0.5.3.2
0.5.3 常用数据结构
1 STL速查
1.1 常用STL操作
reverse(container.begin(), container.end())
:反转sort(container.begin(), container.end(), bool cmp)
:排序unique(container.begin(), container.end())
:先把容器排序,然后去重,返回去重后的结尾迭代器,可以通过.erase()
删除结尾部分shuffle(container.begin(), container.end())
:随机打乱元素(c++14 以前使用random_shuffle()
)lower_bound(a.begin(), a.end(), x)
:找到第一个 >= x的数,返回迭代器(找不到返回尾迭代器)【区别于upper_bound
,upper返回第一个 > x的数】可以用此迭代器减去a.begin()
获取这个数的下标
1.2 vector
- 初始化
1
2
3
4
5vector<int> a(n);
vector<int> b(n,0);
vector<int> c = {1,2,3};
vector<int> d(a); // 将a复制一份为d
vector<vector<int>> e(n,vector<int>(n)); push_back
:在数组末尾插入,emplace_back
更高效pop_back
:在数组末尾删除
1.3 map
映射,底层仍然是红黑树
1 | map<int,int> mp; |
如果要使用hash table,应该使用unordered_map,使用方式与map相同
1.4 set
底层是红黑树
insert
:插入元素lower_bound(x)
:二分查找有序数列,返回大于等于x的最大值(迭代器iterator)erase(x)
:删除迭代器对应的元素,如果x是迭代器 ,是元素则会先find后删除find(x)
:查找 ,返回迭代器- 遍历:使用迭代器
multiset可以插入重复元素,使用erase时只删除一个元素,不会把重复的都删除
利用有序的
set/map
可以自动实现插入自动排序,即将元素放置到对应位置,辅助lower_bound
进行二分查找
1.5 stack
push(x)
:加入元素pop()
:删去栈顶,返回值为voidtop()
:返回栈顶元素
1.6 queue
push(x)
pop()
front()
1.6.1 deque
同时操作头尾,双端队列
push_back(x)
:队尾插入pop_back()
:弹出队尾元素push_front(x)
:队首插入pop_front()
:弹出队首元素
1.6.2 priority_queue
通常作为堆使用
- 初始化
1
2priority_queue<int> q; // 默认大根堆(最大堆)
priority_queue<int, vector<int>, greater<int>> q; //使用vector作为底层容器,小根堆 top()
:访问堆顶元素push(x)
pop()
1.7 pair
使用first/second来访问元素
2 字符串
1 | // 字符串基本操作 |
【int与string互转】
- int转string
c++11中的新特性to_string()
1
2
3
4
5
6
7
8
9
10// c++11
string to_string (int val);
string to_string (long val);
string to_string (long long val);
string to_string (unsigned val);
string to_string (unsigned long val);
string to_string (unsigned long long val);
string to_string (float val);
string to_string (double val);
string to_string (long double val); - string转int:使用
istringstream
2.1 字符串哈希
基本思想:将字符串通过哈希函数转化为int值,
注意点:b是大素数,计算出来的和(很大)需要取模
1 | const int M = 1e9+7; |
代码来自oi-wiki,
i64
表示int64_t
,即c++中的64位带符号整数(头文件写法:using i64 = int64_t;
)
【例题】leetcode 187.Repeated DNA Sequences
2.2 Manacher算法
用于求解字符串中的最大回文串。“abcdsdc"在字符串中加入占位符和开始结束符号,”$#a#b#c#d#s#d#c#^",然后遍历回文串可能的中心位置c,记录该中心位置对应的最大回文半径r
当中心从c移到i的时候,如果i < r,那么当前i的回文半径至少是r-i和关于c对称的位置回文半径r[2c-i]中的最小值,需要根据i周围情况做进一步调整。如果i > r,那就要从1开始扩展。
1 | vector<int> manacher(string& s){ |
计算最长回文子串时,需要注意半径中包含#
占位符,计算长度和起始位置时需要一些转换。可以参考以下例题的实际代码。
【例题】leetcode 5.Longest Palindromic Substring
2.3 KMP算法
不重复匹配已经匹配过的相同前缀
1 | void kmp(string a, string b, vector<int>& next){ |
【例题】leetcode 28.Find the Index of the First Occurrence in a String
2.4 Trie
字典树
可以用链表传统树结构实现,也可以用数组的形式简易实现。
1 | namespace Trie{ |
【例题】leetcode 208.Implement Trie Prefix Tree
3 常用结构
3.1 栈
First in, Last out.
STL提供stack<T>
,也可以用数组简单实现
经典例题:括号匹配问题 leetcode 20.Valid Parentheses
【*单调栈】栈中元素保持单调递增性或递减性,例题请查看单调栈题单
【例题】
3.2 队列
First in, First out.
STL提供queue<T>
,详见1.6节
【经典应用】广度优先搜索BFS(见4.6.1节)
3.2.1 *单调队列
类似于单调栈,队列中元素保持单调性,通常用于滑动窗口问题。一般多用双端队列deque
实现,但是deque常数较大,可以用数组替代。
通常用于滑动窗口问题。
【例题】leetcode 239.Sliding Window Maximum
3.2.2 优先队列/堆
优先队列使用堆实现,STL提供priority_queue
(见1.6.2节)
使用数组实现堆(大根堆),元素从1开始
1 | vector<int> heap; |
经典应用:求第k大的元素
【例题】leetcode 218.The Skyline Problem
3.3 哈希表
STL提供unordered_set
查找元素是否在集合中,用unordered_map
存储键值对
【例题】leetcode 128.Longest Consecutive Sequence
一个很妙的原地hash技巧,即利用数组下标的性质,将数组中元素变为
-
或者插入一个数位,保留原来数据的性质的同时也能标记其他数据,节省空间
查看例题:leetcode 41.First Missing Positive
3.4 并查集
基本思想:标记每一个节点的父节点
基本操作:
- 查询根节点(get)
- 合并树(merge)
并查集有两种优化方式,路径压缩和按秩合并
3.4.1 路径压缩
基本思想:每次get(x)后将x的根节点设置为查询到的节点
1 | namespace UFS{ |
3.4.2 按秩合并
按秩合并和路径压缩不能一起使用。
- 按树的节点数量合并
- 按树的深度(秩)合并
1 | namespace UFS{ |
【例题】
- leetcode 684.Redundant Connection
- leetcode 765.Couples Holding Hands
- 带权并查集 leetcode 399.Evaluate Division
leetcode标签:并查集
3.5 链表
3.6 树
【遍历】
- 前序
- 中序
- 后序
- 层次
【完全二叉树】
只有叶子层可以有空节点,其他层必须拥有全部节点,且叶子层节点必须从左向右依次排列
【满二叉树】
所有节点如果不是叶子节点必须有两个子结点。
3.6.1 BST
3.6.2 B/B+ Tree
3.6.3 Balanced Tree
3.6.4 Red Black Tree
3.6.5 LCA
寻找lowest common ancester
3.6.5.1 递归
查看左右子树是否包含该节点
1 | TreeNode* lca(TreeNode* root,TreeNode* l,TreeNode* r){ |
3.6.5.2 倍增
3.6.6 最长路径/树的直径
3.6.6.1 两次BFS
- 从任意节点p出发,利用广度优先/深度优先找到最长路径x的终点i
- 从i出发,找到最长路径y
- y即树的最长路径
任意节点出发寻找的最长路径终点一定是叶子节点,从叶子节点出发寻找的最长路径即为整棵树的最长路径
3.6.6.2 树上DP
设表示到x点最长的叶子节点的距离,那么,是的子节点。
如果是的两个不一样的子节点(和不在同一条子节点路上),那么可以把树的直径用更新一下。通过dp可以遍历节点,相当于每次考虑经过一个节点最长路径。
1 | int f[N],ans = 0; |
3.7 *树状数组
3.7.1 一维
用来解决“单点修改,单点查询”,“单点修改,区间查询”,“区间查询,区间修改”这些问题,这篇博客详细解释了树状数组的求和原理
lowbit:数字最低为1位表示的数字大小(例如,lowbit(1) = 1
,lowbit(5) = 1
,lowbit(6) = 2
树状数组中存储的元素,表示区间[i-lowbit[i]+1,i]
的区间查询元素
单点修改,区间查询模板
1 | namespace Bit{ |
树状数组修改和查询的时间复杂度均为
如何构建树状数组?
使用前缀和之类的手段
【例题】
- leetcode 307.Range Sum Query - Mutable
- leetcode 315.Count Of Smaller Numbers After Self
- 二维树状数组:leetcode 308.Range Sum Query 2D - Mutable
3.7.2 二维
3.8 *线段树
线段树适合用来解决“区间修改、区间查询”的问题。
将每一段区间按照叶节点记录子区间,父节点记录将子区间合并成一个大区间。修改区间时,针对该节点以下子节点的更新,使用lazy tag标记,访问时将tag下推。
- 区间修改代码(区间和)
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
51namespace SegTree{
int s[N << 2], t[N << 2]; //s数组用于存储线段树区间和,t数组用于记录tag
inline void up(int p){s[p] = s[lc]+s[rc];} // 向上更新
inline void down(int p, int l, int r){
// 使用tag向下更新
if(!tag[p]) return;
s[lc] += tag[p]*(mid-l+1);tag[lc] += tag[p];
s[rc] += tag[p]*(r-mid); tag[rc] += tag[p];
tag[p] = 0;
}
// 使用递归技巧建树
void build(int p, int l, int r){
if(l == r){
s[p] = a[l];
return;
}
build(lc,l,mid);
build(rc,mid+1,r);
up(p);
}
//修改某个区间:[x,y]区域内的值+v
void modify(int p, int l, int r,int x,int y,int v){
if(x <= l && y >= r){ // 递归结束,找到包括的全部区间
s[p] += (r-l+1)*v;
tag[p] += v;
return;
}
down(p,l,r);
if(x <= mid) modify(lc,l,mid,x,y,v);
if(y > mid) modify(rc,mid+1,r,x,y,v);
up(p);
}
//查询某个区间内的值
int query(int p,int l,int r,int x,int y){
if(x <= l && y >= r){
return s[p];
}
down(p,l,r);
int ret = 0;
if(x <= mid) ret += query(lc,l,mid,x,y);
if(y > mid) ret += query(rc,mid+1,r,x,y);
return ret;
}
} - 单点修改
1
2
3
4
5
6
7
8
9
10void modify(int p,int l,int r,int x,int v){
if(l == r){
s[p] += v;
return;
}
down(p,l,r);
if(x <= mid) modify(lc,l,mid,x,v);
else modify(rc,mid+1,r,x,v);
up(p);
} - 单点查询
1
2
3
4
5
6
7
8int query(int p,int l,int r,int x){
if(l == r){
return s[p];
}
down(p,l,r);
if(x <= mid) return query(lc,l,mid,x);
return query(rc,mid+1,r,x);
}
线段树的时间复杂度为
如何修改模板以适应不同的问题
- 区间复合如何实现,即如何实现up函数
- 区间更新操作如何实现,即如何实现down和modify函数
3.9 ST表
这篇博客详细解释了ST表的原理。简单解释就是利用了区间可加性的思想,即区间[l,r]的性质可以由[l,s]和[s+1,r]两个区间的相同性质合并而来。例如:max(l,r) = max(max(l,s),max(s+1,r))
在ST表中,我们考察区间这个区间,可以发现这个区间可以由和这两个区间,其中,每个子区间包含了个元素,总共区间有个元素
查询时,区间中有r-l+1个元素,因此取,此时区间和可以交叉覆盖整个查询区间。
以下模板以求解区间最大值为例:
1 | int st[N][M]; // 数组一共有N个元素,M = log2(N),可以根据题目范围适当开大一点 |
区间问题的总结请查看4.8节前缀和与差分数组
3.10 分块
将数组分成几个区间,使用tag标记区间统一操作情况,不在区间中的零散数据直接暴力处理。
可以将区间内的数据进行排序以获得更高的性能。
一般来说还是优先考虑线段树等log数据结构
4 技巧
4.1 动态规划
原理:保存子问题的解避免重复计算
核心:找到状态转移方程
4.1.1 一维动态规划
-
爬楼梯问题:多种传递方式叠加
-
打家劫舍问题:有限制的叠加方式
-
不考虑顺序,即组合类问题:通过枚举可能的选项,固定顺序求解,具体例题请查看coin change II
4.1.2 二维动态规划
4.1.3 分割类型
4.1.4 子序列与字符串
- 连续子序列:选定结尾枚举(有些题目可以考虑前缀和方法)
- 非连续型子序列:
- 【经典问题】最长公共子序列(见题单线性dp专题)
- 【经典问题】最长递增子序列
【最长公共子序列的并行计算问题】
所有的并行计算问题都可以使用拓扑排序的思想解决
4.1.5 背包问题
- 0-1背包:每个物体只能选1次或0次,通过限制体积(或其他等价限制)来遍历
- 完全背包
4.1.6 状态机
4.1.7 树形dp
4.1.8 区间dp
4.1.9 状态压缩dp
4.2 贪心
4.2.1 区间问题
4.3 多指针与滑动窗口
4.3.1 双指针
4.3.2 滑动窗口
4.4 二分法
二分查找模板:
找到大于等于/大于/小于等于/小于:
4.5 排序
4.5.1 归并排序
divide & conquer
1 | void helper(vector<int>& nums,int l,int r,vector<int>& temp){ |
【例题】很精妙的运用LCR170. 交易的逆序对总数
4.5.2 快速排序
4.6 搜索
4.6.1 广度优先BFS
使用队列来存储每一层可以到达的节点
- 树BFS:层次遍历
- 图BFS
BFS可以较为容易的实现计算深度(层数/路径长度),但是难以记录路径
【例题】leetcode 934.Shortest Bridge
【专题】如何在BFS中找到最短路径(记录路径)
访问题目leetcode 126.Word Ladder II
4.6.2 深度优先DFS
使用栈结构,不要忘记访问过的标记避免重复访问
【例题】
4.6.3 其他搜索
【例题】变形搜索leetcode 240.Search a 2D Matrix II
图上BFS/DFS搜索相关题单请查看图论部分总题单
4.7 位运算
【常见技巧】
n&(n-1)
:消去n最低位的1n&(-n)
:获取最低位的1的大小reverse(lowbit(reverse(x)))
:获取最高位1,reverse是把进制位头尾颠倒
【解题思想】
and
参与运算的数越多,得到的结果越小;or
参与运算的数越多,得到的结果越大and/or
运算可以把参与运算的整数拆分为二进制32位,按照每一位考虑
4.8 前缀和与差分数组
前缀是一个能解决很多问题的思想。
4.8.1 前缀和
对于任意一个数组
构造前缀和数组,求解区间 的和可以用复杂度求出
4.8.2 差分数组
构造差分数组,对区间做相同操作时(如同时加上)可以以复杂度实现。
【例题】leetcode 560.Subarray Sum Equals K
4.8.3 区间求值问题总结
- 数组不变,求区间和:「前缀和」、「树状数组」、「线段树」
- 多次修改某个数,求区间和:「树状数组」、「线段树」
- 多次整体修改某个区间,求区间和:「线段树」、「树状数组」(看修改区间的数据范围)
- 多次将某个区间变成同一个数,求区间和:「线段树」、「树状数组」(看修改区间的数据范围)
总而言之,「线段树」的扩展性最佳,树状数组通常可以简化线段树。
4.8.4 前后缀分解
- 两个非重叠子数组的最大和 1680
- 三个无重叠子数组的最大和
- 找到所有好下标 1695
- 适合野炊的日子 1702
- 使字符串平衡的最少删除次数 1794
- 将字符串翻转到单调递增
- 找两个和为目标值且不重叠的子数组 1851
- 得到山形数组的最少删除次数 1913
- 除自身以外数组的乘积 ~2000
- 使二进制字符串字符交替的最少反转次数 2006
- 构造乘积矩阵 2075
- 移除所有载有违禁货物车厢所需的最少时间 2219
- 统计回文子序列数目 2223
- 删除元素后和的最小差值 2225
- 最少得分子序列 2432
- 统计上升四元组 2433
- 执行操作后的最大分割数量 3039
- 最大连续 1 的个数 II(会员题)
- 经过一次操作后的最大子数组和(会员题)
4.9 回溯
4.10 曼哈顿距离
将坐标系顺时针旋转45°并拉伸为倍,可以将两点之间的曼哈顿距离转换为在x’轴或者y’轴上的投影(即相对距离)。
坐标转换:(x,y) -> (x+y,x-y)
5 图论
图论系列题单
5.1 图的基本表示方法
5.1.1 邻接矩阵
邻接矩阵适用于点比较少的情况,用一个二维数组存储相互连接的点之间的权值(d[i][j] = v
)
5.1.2 链式前向星
采用与每个点相连的边链表形式存储。
1 | int tot, head[N]; // tot表示当前边的下标(总数),head记录从这个点出发的所有边链表的头节点 |
5.2 图遍历
- DFS
- BFS
- 拓扑排序
5.3 最短路
5.2.1 floyd
时间复杂度
floyd的思想是动态规划,可以视作经过k的i到j最短路径和不经过k的最短路径。
1 | int f[N][N],d[N][N]; // f存储最短路径,d存储邻接矩阵 |
如果i到j不存在路径,可以取无穷,为了防止溢出,可以设d[i][j] = 0x3f3f3f3f
向图中新加一条通路,则需要更新经过此条通路和不经过这条通路的最小路径
1 | for(int i = 1;i <= n;i++){ |
5.2.2 dijkstra
基本思想:S={u},每次计算到S集合距离最近的点加入集合。(贪心)
5.2.2.1 朴素实现
时间复杂度
1 | int d[N]; |
5.2.2.2 堆优化
算法瓶颈在于求最小值
经过堆优化后的时间复杂度为,m表示边的数量
1 | using pii = pair<int,int>; |
5.2.3 Bellman-Ford
用于处理负权边,实现原理:用以下不等式更新dis
经过n轮迭代,可以保证就是最短路。如果n轮迭代后上述等式不成立(即还可以更新),表明图中存在负环。
该算法时间复杂度为,n为点的数目,m为边的数目。这一时间复杂度是稳定的。
5.2.3.1 朴素实现
1 | int d[N]; |
5.2.3.2 spfa
队列优化的BellmanFord算法:类似bfs的思想,只有x更新,与x相连的点才会发生更新,因此使用队列存储被更新过的节点,然后更新相连的节点。
1 | int d[N]; |
5.4 最小生成树
minimum spanning tree - MST
5.4.1 Kruskal算法
基于贪心的思想,每次向kruskcal加入没有联通的最小权值的边。时间复杂度为,主要瓶颈在于给边排序的时间复杂度。
使用并查集记录连通块。
1 | struct Edge{int x,y,v;}; |
5.4.2 Prim算法
类似dijkstra算法,每次将到当前连通块中边最小的点加入联通集合。
算法复杂度为,瓶颈主要在找最小值的过程。
1 | bool vis[N]; // 用于记录当前点是否加入生成树 |
5.5 二分图
5.6 网络流
6 数学
6.1 带余除法
6.1.1 最大公约数
辗转相除法(欧几里得法)求解最大公约数,
时间复杂度: 【证明:考虑 和 两种情况,可以证明每次整除会导致规模缩小一半】
实现与使用:
- c++14:
__gcd(int,int)
(在<algorithm>
库中) - c++17:
gcd(int,int)
(在<numeric>
库中)
【例题】leetcode 1447.Simplified Fractions
6.1.2 不定方程
【定义】未知数个数超过方程数量,典型例子,为未知数,为参数
【裴蜀定理】
简单证明:假设$a>b, a=b\cdot k+r $ ,那么 可以写作
在上述式子中,可以写作 ,可以写作 (此处可利用c++中整数相除特性),因此扩展欧几里得算法可以写成:
1 | int extgcd(int a,int b,int& x,int& y){ //此处默认c=gcd(a,b) |
【例题】leetcode 365.Water And Jug Problem
6.2 *素数/质数
6.2.1 质因数分解
任何数都可以被写成唯一的质因数分解形式 ,其中 是质数。
推理可知,如果要对一个整数进行质因数分解,只需要枚举 之间的素数(n最多只有一个大于的质因数),时间复杂度为
1 | using pii = pair<int,int>; |
6.2.2 素数筛
- 埃氏筛:枚举过程中将质数的倍数标记置为合数 埃氏筛的时间复杂度为 ?
1
2
3
4
5
6
7
8
9
10
11
12
13int countPrimes(int n){
int cnt = 0;
vector<bool> isPrime(n+1,true);
for(int i = 2;i <= n;i++){
if(isPrime[i]){
cnt++;
for(int j = i+i;j <= n;j+=i){
isPrime[j] = false;
}
}
}
return cnt;
} - 欧拉筛:每个数被严格筛选一次,所以时间复杂度是 使用合数与该合数的最小质因数构造新的合数,假设a是一个合数,a = p * b,b是合数。假设存在 a = p’ * c,c是另一个合数,不妨设c > b(按照遍历次序),p’ < p,p’和p互质,因此p’|b,即 b%p’== 0,退出循环,不可能再出现p。详细证明请参考这篇博客
1
2
3
4
5
6
7
8
9
10
11int primeCnt = 0,primes[N];
bool notPrime[N];
void sieve(int maxN){
for(int i = 2;i <= maxN;i++){
if(!notPrime[i]) primes[++primeCnt] = i;
for(int j = 1; j <= primeCnt && i*primes[j] <= maxN;j++){
notPrime[i*primes[j]] = true;
if(i % primes[j] == 0) break;
}
}
}
素数筛的进阶用法:筛选积性函数
6.2.3 判断质数
【法一】从2到遍历,查看是否能被整除
【法二】使用素数筛预处理范围内所有的素数
6.3 乘法逆元
乘法逆元:,x是模P意义下a的乘法逆元,等价于求解,转换为不定方程使用扩展欧几里得算法求解。因此有解要求
6.3.1 费马小定理
费马小定理:为质数,
由费马小定理可以推出模P意义下a的乘法逆元特解是
6.3.2 线性递推
线性递推关系可以求解[1,n]每个整数对于P的逆元
【总结】求解乘法逆元的方法
- 扩展欧几里得算法
- 费马小定理+快速幂直接求解
- 线性递推
这一部分的参考代码可以看这篇博客
6.3.3 快速幂
计算,传统做法是依次乘,时间复杂度为。根据二分法的思想,可以计算将时间复杂度化为
递归写法:注意乘方会导致int类型数据溢出,因此使用long long类型并取模
1 | typedef long long ll; |
循环写法:节省栈开销。可以写成. 通过把指数转化为二进制进行计算。
1 | typedef long long ll; |
6.4 矩阵计算
6.4.1 高斯消元
求解
基本思想:构造矩阵[A,b]
- 从上往下消元 -> 形成三角矩阵
- 从下往上消元 -> 形成对角矩阵
1 | const double eps = 1e-7;// 精度值 |
6.4.2 矩阵乘法
可以用于处理一些递推关系,比如斐波那契数列
【矩阵求幂】快速幂优化。下述模板只是矩阵乘法的实现
1 | class Matrix{ |
6.5 组合数
求解组合数可以通过递归和阶乘两种方式求解
6.5.1 杨辉三角
杨辉三角揭示了组合数的递归规律,。同时对于任意
1 | void getBinom(int n){ |
该方法的复杂度为
6.5.2 组合阶乘
原理:,计算阶乘复杂度,可以直接使用除法,也可以使用前面所说的乘法逆元(当对大数取模且P是质数时)
1 | int fac[N], ifac[N]; // fac记录阶乘,ifac记录阶乘对应模P逆元 |
6.6 随机
- 带有权重的随机leetcode 528.Random Pick with Weight
- 用某个范围随机数生成另一些随机数leetcode 470.Implement Rand10 Using Rand7
- 使用随机数计算:利用面积公式,计算随机点落入圆的概率
6.6.1 java随机数
1 | Random rand = new Random(); |
Math.random()
随机生成[0.0,1.0)之间的double类型数据
6.6.2 c++随机数
1 | int t = rand() % num; |
6.7 求解平方根
6.7.1 二分法
初始化为x/2,然后不断除2逼近
6.7.2 牛顿迭代法
设置一个近似值x(可以是n/2),然后不断用迭代逼近
画图可以得到以下递推关系:
6.8 回文数字
从小到大依次生成回文数字,基本思想:固定回文根,如12,那么可以生成奇数位和偶数位两种回文数,121和1221.
算法过程:枚举范围内所有回文根(按照位数枚举)
1 | // n表示最大范围数位 |