学习平台 AcWing 算法基础课
基础算法 快速排序 算法思想 分治
分支算法可分为三步:
代码模板
1 2 3 4 5 6 7 8 9 10 11 12 13 void Quick_sort (int p[],int l,int r) { if (l >= r) return ; int x = p[l + r >> 1 ], i = l - 1 , j = r + 1 ; while (i < j){ do i++ ;while (p[i] < x); do j-- ;while (p[j] > x); if (i < j) swap (p[i],p[j]); } Quick_sort (p, l, j); Quick_sort (p, j + 1 , r); }
归并排序 算法思想 分治
代码模板
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 void Merge_sort (int p[],int l,int r) { if (l >= r)return ; int mid = l + r >> 1 ; Merge_sort (p,l,mid); Merge_sort (p,mid+1 ,r); int k=0 ,i = l,j = mid + 1 ; while (i <= mid && j <= r) if (p[i] <= p[j]) tmp[k ++] = p[i ++]; else tmp[k ++] = p[j ++]; while (i <= mid) tmp[k ++]=p[i ++]; while (j <= r) tmp[k ++]=p[j ++]; for (i = l, j = 0 ;i <= r; i ++, j ++) p[i]=tmp[j]; }
二分查找 算法思想 二分
实现思想 假定目标值在区间[l, r] 中,每次将区间长度缩小一半,当l = r 时,则可以取到目标值。
整数二分查找存在两个模板,取决于中间值的取值方法:
代码模板
1 2 3 4 5 6 7 8 int bsearch (int l, int r) { while (l < r){ int mid = l + r >> 1 ; if (check (mid)) r = mid; else l = mid + 1 ; } return l; }
代码模板
1 2 3 4 5 6 7 8 int bsearch (int l, int r) { while (l < r){ int mid = l + r + 1 >> 1 ; if (check (mid)) l = mid; else r = mid - 1 ; } return l; }
高精度加法 算法思想 大数加法 ;
代码模板
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 vector<int > add (vector<int > &A, vector<int > &B) { if (B.size () > A.size ()) return add (B,A); vector<int > C; int t = 0 ; for (int i = 0 ;i < A.size (); i ++){ t += A[i]; if (i < B.size ()) t += B[i]; C.push_back (t % 10 ); t /= 10 ; } if (t) C.push_back (t); return C; }
高精度减法 算法思想 大数减法 ;
代码模板
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 bool bmp (vector<int > &A, vector<int > &B) { if (A.size () != B.size ()) return A.size () > B.size (); for (int i = A.size () - 1 ; i >= 0 ; i --) if (A[i] != B[i]) return A[i] > B[i]; return true ; } vector<int > sub (vector<int > &A, vector<int > &B) { vector<int > C; int t= 0 ; for (int i=0 ;i < A.size ();i++){ t = A[i] - t; if (i < B.size ()) t -= B[i]; C.push_back ((t + 10 ) % 10 ); if (t < 0 ) t = 1 ; else t=0 ; } while (C.size () > 1 && C.back () == 0 ) C.pop_back (); return C; }
高精度乘低精度 算法思想 大数乘法
代码模板
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 vector<int > mul (vector<int > &A, int b) { vector<int > C; int t = 0 ; for (int i = 0 ; i < A.size () || t; i ++ ) { if (i < A.size ()) t += A[i] * b; C.push_back (t % 10 ); t /= 10 ; } while (C.size () > 1 && C.back () == 0 ) C.pop_back (); return C; }
高精度除以低精度 算法思想 大数除法
代码模板
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 vector<int > div (vector<int > &A, int b, int &r) { vector<int > C; r = 0 ; for (int i = A.size () - 1 ; i >= 0 ; i -- ) { r = r * 10 + A[i]; C.push_back (r / b); r %= b; } reverse (C.begin (), C.end ()); while (C.size () > 1 && C.back () == 0 ) C.pop_back (); return C; }
前缀和 什么是前缀和
原数组:a[1], a[2], a[3], a[4], a[5], …, a[n] 前缀和:S[i] 为数组的前 i项和 前缀和:S[i] = a[1] + a[2] + a[3] + … + a[i]
为方便求解,以及后续方便计算,通常令S[0] = 0 ,原数组 以及前缀和数组 的下标从1开始。
前缀和的作用
快速求出元素组中某段区间的和。
代码模板
1 2 3 4 5 6 7 int pSum (int arr[]) { int sum[N]; sum[0 ] = 0 ; for (int i = 1 ; i <= n; i++){ sum[i] = arr[i] + sum[i - 1 ]; } }
二维数组求解前缀和
计算过程 s[i, j] = s[i - 1, j] + s[i, j - 1] + s[i - 1, j - 1] + a[i, j]
同样的为了方便计算,二维数组下标也需要从 1 开始
代码模板
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 #include <iostream> using namespace std;const int N = 1010 ;int a[N][N], s[N][N];int main () { int n, m, q; cin >> n >> m >> q; for (int i = 1 ; i <= n; i++) for (int j = 1 ; j <= m; j++) { scanf ("%d" , &a[i][j]); s[i][j] = s[i][j - 1 ] + s[i - 1 ][j] - s[i - 1 ][j - 1 ] + a[i][j]; } while (q--) { int x1,y1,x2,y2; scanf ("%d%d%d%d" , &x1, &y1, &x2, &y2); printf ("%d\n" , s[x2][y2] - s[x2][y1 - 1 ] - s[x1 - 1 ][y2] + s[x1 - 1 ][y1 - 1 ]); } return 0 ; }
差分 差分
和 前缀和
是一对互逆运算
定义 对于一个给定的数列A,他的差分序列B定义为:
B[1] = A[1], B[i] = A[i] - A[i - 1](2 <= i <= n)
数据结构 单链表 这里采用数组模拟 链表,因为采用new Node()的过程是比较慢的;
单链表:可以用于实现邻接表(存储树或图)
双链表:优化某些问题
实现方法 单链表使用数组模拟的方法:使用两个数组e[n] ; ne[n] 其中e[i] 存放第i个元素的值,ne[i] 存放第i个元素的后继下标。
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 #include <iostream> using namespace std;const int N = 100010 ;int head, e[N], ne[N], idx;void init () { head = -1 ; idx = 0 ; } void add_to_head (int x) { e[idx] = x; ne[idx] = head; head = idx; idx ++; } void del (int k) { ne[k] = ne[ne[k]]; } void add (int k, int x) { e[idx] = x; ne[idx] = ne[k]; ne[k] = idx ++; } int main () { init (); return 0 ; }
双链表 同样使用数组模拟双链表,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include <iostream> using namespace std;const int N = 100010 ;int e[N], l[N], r[N], idx;void add (int k, int x) { e[idx] = x; r[idx] = r[k]; l[idx] = k; r[k] = idx; l[r[idx]] = idx ++; } void remove (int k) { r[l[k]] = r[k]; l[r[k]] = l[k]; }
栈 采用数组模拟栈
实现方法 top 栈顶指针,当top == 0 时栈空,stk[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 #include <iostream> #include <string> using namespace std;const int N = 100010 ;int top, stk[N];int main () { int m; cin >> m; while (m --){ string op; int x; cin >> op; if (op == "push" ){ cin >> x; stk[++ top] = x; } else if (op == "pop" ) top --; else if (op == "query" ) cout << stk[top] <<endl; else cout << (top ? "NO" : "YES" ) << endl; } return 0 ; }
队列 采用数组模拟队列
实现方法 tail 队尾指针,head 队头指针,当head == 0 || head > tail 时队空,que[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 #include <iostream> #include <string> using namespace std;const int N = 100010 ;int head, que[N], tail;int main () { int m; cin >> m; while (m--){ string op; cin >> op; int x; if (op == "push" ){ cin >> x; que[++tail] = x; if (head == 0 ) head++; } else if (op == "pop" ) head ++; else if (op == "query" ) cout << que[head] << endl; else { if (head == 0 || tail < head) cout << "YES" << endl; else cout << "NO" << endl; } } }
KMP 定义 是一个字符串匹配算法,对暴力的那种一一比对的方法进行了优化,使时间复杂度大大降低,
算法习惯 下标从1开始;存在一个模式串
、一个模板串
算法精讲
KMP字符串
算法思路
s[n]
模式串:即比较长的字符串
p[m]
模板串:即比较短的字符串
ne[m]
部分匹配值表:存储的是每一个下标对应的部分匹配值
p[1, j] == p[i - ne[j] + 1, i]
核心思想:在每次匹配失败后,不是把p串向后移动一位,而是把p串往后移动至下一次可以和前面部分匹配的位置,这样就可以跳过大多数的失配步骤。而每次p串移动的步数就是通过查找ne[ ]
数组确定的。
匹配思路和实现代码
KMP主要分为两步:求解ne数组 ;匹配字符串
s[n]
和p[m]
都是从1开始的,
因为p[m]
下标从1开始,所以ne[1] = 0
,求解ne[]
时直接从下标为2开始遍历,i 。j表示当前前面最多匹配了j个字符。
1 2 3 4 5 6 7 8 9 for (int i = 2 , j = 0 ; i <= m; i++) { while (j && p[i] != p[j + 1 ]) j = next[j]; if (p[i] == p[j + 1 ]) j++; next[i] = j; }
i 从1开始遍历s数组,j 从0开始遍历p数组。每次将s[i]
和p[j + 1]
进行比较。若相等,则匹配;若碰到第i个字符不匹配,则直接将j = ne[j] ,i 不变。通过将遍历p的指针前移来达到p后移的效果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 for (int i = 1 , j = 0 ; i <= n; i++){ while (j && s[i] != p[j + 1 ]) j = ne[j]; if (s[i] == p[j + 1 ]) j++; if (j == m) { j = next[j]; } }
Trie 树 算法定义 用于快速存储和查找字符串集合的树。
实现思想
son[N][26]
存放小写字母,因此每个节点最多可以扩展出26边,每代表一个点,每列代表该节点扩展出一条边。
cnt[i]
以当前第i个结尾的有单词则为1,否则为0。
idx
下标,idx为0时,即代表根节点,又是空节点。
代码模板
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 #include <iostream> using namespace std;const int N = 100010 ;char str[N];int son[N][26 ], cnt[N], idx;void insert (char str[]) { int p = 0 ; for (int i = 0 ; str[i]; i++) { int u = str[i] - 'a' ; if (!son[p][u]) son[p][u] = ++idx; p = son[p][u]; } cnt[p] ++; } int query (char str[]) { int p = 0 ; for (int i = 0 ; str[i]; i++) { int u = str[i] - 'a' ; if (!son[p][u]) return 0 ; p = son[p][u]; } return cnt[p]; }
并查集 目的: 在近乎O(1)
的时间复杂度内支持下面两个操作
1.将两个集合合并;
2.询问两个元素是否在一个集合中;
原理: 每个集合用一颗树表示,树根的编号就是整个集合的编号,每个节点存储它的父节点,p[x]
表示x的父节点。若p[x] == x
,则x为根节点
问题1: 如何判断树根节点: if(p[x] == x)
问题2: 如何求x的集合编号:while(p[x] != x) x = p[x] ;
问题3: 如何合并两个集合: p[x]
是x的集合编号,p[y]
是y的集合编号,则p[x] = y ;
优化:路径压缩: 当查询过一遍后,将路径上所有的节点直接指向根节点。
代码模板 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include <iostream> using namespace std;int p[N]; int find (int x) { if (p[x] != x) p[x] = find (x); return p[x]; } int merge (int x, int y) { p[find (x)] = find (y); } bool Judge (int x, int y) { if (find (x) == find (y)) return true ; return false ; }
堆 作用 维护一个数据集合;
需要实现的操作
插入一个数;heap[++size] = x; up(size)
求集合当中的最小值;heap[1];
删除最小值;heap[1] = heap[size]; size—; down(1);
删除任意元素;heap[k] = heap[size]; size—; down(k); up(k);
修改任意元素;heap[k] = x; down(size); up(size);
基本结构 是一颗完全二叉树
(除了最后一层节点之外,上层节点都是非空,最后一层节点是从左到右依次排布)
存储数据结构 使用数组存储,1号节点是根节点(下标从1开始),左儿子是2x
;右儿子是2x+1
h[N]
存储堆中的值, h[1]是堆顶,x的左儿子是2x
, 右儿子是2x + 1
ph[k]
存储第k个插入的点在堆中的位置
hp[k]
存储堆中下标是k的点是第几个插入的
size
堆中元素的个数
需要的操作
down(x)
将x节点往下移
up(x)
将x节点往上移
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 int h[N], ph[N], hp[N], size;void heap_swap (int a, int b) { swap (ph[hp[a]],ph[hp[b]]); swap (hp[a], hp[b]); swap (h[a], h[b]); } void down (int u) { int t = u; if (u * 2 <= size && h[u * 2 ] < h[t]) t = u * 2 ; if (u * 2 + 1 <= size && h[u * 2 + 1 ] < h[t]) t = u * 2 + 1 ; if (u != t) { heap_swap (u, t); down (t); } } void up (int u) { while (u / 2 && h[u] < h[u / 2 ]) { heap_swap (u, u / 2 ); u >>= 1 ; } } for (int i = n / 2 ; i; i -- ) down (i);
哈希表
存储结构 :下面两种方法是为了解决映射冲突的。映射冲突是指将两个不同的数映射到同一个数上。
开放寻址法
拉链法:形状类似于邻接表,将映射到同一个位置的数用一个链放在下面。
字符串哈希方式
作用 :将一堆较大范围的数映射到较小范围的一堆数中。
STL简介 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 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 vector, 变长数组,倍增的思想 size () 返回元素个数 empty () 返回是否为空 clear () 清空 front ()/back () push_back ()/pop_back () begin ()/end () [] 支持比较运算,按字典序 pair<int , int > first, 第一个元素 second, 第二个元素 支持比较运算,以first为第一关键字,以second为第二关键字(字典序) string,字符串 size ()/length () 返回字符串长度 empty () clear () substr (起始下标,(子串长度)) 返回子串 c_str () 返回字符串所在字符数组的起始地址 queue, 队列 size () empty () push () 向队尾插入一个元素 front () 返回队头元素 back () 返回队尾元素 pop () 弹出队头元素 priority_queue, 优先队列,默认是大根堆 size () empty () push () 插入一个元素 top () 返回堆顶元素 pop () 弹出堆顶元素 定义成小根堆的方式:priority_queue<int , vector<int >, greater<int >> q; stack, 栈 size () empty () push () 向栈顶插入一个元素 top () 返回栈顶元素 pop () 弹出栈顶元素 deque, 双端队列 size () empty () clear () front ()/back () push_back ()/pop_back () push_front ()/pop_front () begin ()/end () [] set, map, multiset, multimap, 基于平衡二叉树(红黑树),动态维护有序序列 size () empty () clear () begin ()/end () ++, -- 返回前驱和后继,时间复杂度 O (logn) set/multiset insert () 插入一个数 find () 查找一个数 count () 返回某一个数的个数 erase () (1 ) 输入是一个数x,删除所有x O (k + logn) (2 ) 输入一个迭代器,删除这个迭代器 lower_bound () /upper_bound () lower_bound (x) 返回大于等于x的最小的数的迭代器 upper_bound (x) 返回大于x的最小的数的迭代器 map/multimap insert () 插入的数是一个pair erase () 输入的参数是pair或者迭代器 find () [] 注意multimap不支持此操作。 时间复杂度是 O (logn) lower_bound () /upper_bound () unordered_set, unordered_map, unordered_multiset, unordered_multimap, 哈希表 和上面类似,增删改查的时间复杂度是 O (1 ) 不支持 lower_bound () /upper_bound () , 迭代器的++,-- bitset, 圧位 bitset<10000> s ; ~, &, |, ^ >>, << ==, != [] count () 返回有多少个1 any () 判断是否至少有一个1 none () 判断是否全为0 set () 把所有位置成1 set (k, v) 将第k位变成v reset () 把所有位变成0 flip () 等价于~ flip (k) 把第k位取反
搜索与图论 DFS与BFS 对比
数据结构
空间
性质
DFS
stack
O(h)
不具有最短性
BFS
queue
O(2^h^)
“最短路”
深度优先搜索 DFS 算法: 回溯
、剪枝
宽度优先搜索 BFS 类似于层序遍历
树与图的遍历:拓扑排序 树是一种特殊的图
树与图的存储
有向图
邻接矩阵:二维数组,适用于稠密图,T(n) = O(n ^ 2)
邻接表:n个节点对应n个单链表,适用于稀疏图。
邻接表
h[N]
:所有头节点的集合
e[N]
:存每个节点的值
ne[N]
:存每个节点的next值
1 2 3 4 5 6 7 8 9 10 11 12 int h[N], e[N], ne[N], idx;void add (int a, int b) { e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ; } idx = 0 ; memset (h, -1 , sizeof h);
树与图的深度优先遍历 只考虑有向图即可。
时间复杂度:O(n + m)
,n表示点数,m表示边数
代码模板
1 2 3 4 5 6 7 8 9 10 int dfs (int u) { st[u] = true ; for (int i = h[u]; i != -1 ; i = ne[i]) { int j = e[i]; if (!st[j]) dfs (j); } }
树与图的宽度优先遍历
需要使用到队列,在对二叉树使用宽度有点遍历时,其实就是对二叉树进行层序遍历。
代码模板
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 queue<int > q; st[1 ] = true ; q.push (1 ); while (q.size ()){ int t = q.front (); q.pop (); for (int i = h[t]; i != -1 ; i = ne[i]) { int j = e[i]; if (!st[j]) { st[j] = true ; q.push (j); } } }
拓扑排序
时间复杂度:O(n + m)
,n表示点数,m表示边数
代码模板
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 bool topsort () { int hh = 0 , tt = -1 ; for (int i = 1 ; i <= n; i ++ ) if (!d[i]) q[ ++ tt] = i; while (hh <= tt) { int t = q[hh ++ ]; for (int i = h[t]; i != -1 ; i = ne[i]) { int j = e[i]; if (-- d[j] == 0 ) q[ ++ tt] = j; } } return tt == n - 1 ; }
最短路径问题 常见的最短路径
问题 :n点的数量,m边的数量。
单源最短路径
所有边权都是正数
朴素Dijkstra
算法 :O(n^2)
适用于稠密图
堆优化版的Dijkstra
算法:O(m * logn)
适用于稀疏图
存在负权边
Bellman-Ford
: O(nm)
SPFA
: 一般是O(m)
,最坏是O(nm)
多源汇最短路径
:
朴素Dijkstra
使用邻接矩阵
存储,有向图:无向图是一种特殊的有向图。
s[i]
:当前已确定最短路径的点。
dist[i]
:第i
个节点距离起点(1号点)的距离。初始化dist[1] = 0
、disst[i] = +Max
g[N][N]
:存储每条边,下标从1开始
代码模板
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 int g[N][N]; int dist[N]; bool st[N]; int dijkstra () { memset (dist, 0x3f , sizeof dist); dist[1 ] = 0 ; for (int i = 0 ; i < n - 1 ; i ++ ) { int t = -1 ; for (int j = 1 ; j <= n; j ++ ) if (!st[j] && (t == -1 || dist[t] > dist[j])) t = j; for (int j = 1 ; j <= n; j ++ ) dist[j] = min (dist[j], dist[t] + g[t][j]); st[t] = true ; } if (dist[n] == 0x3f3f3f3f ) return -1 ; return dist[n]; }
堆优化版的Dijkstra
使用堆对朴素Dijkstra
算法进行优化
堆:使用c++里面的优先队列。
数据结构:因为针对稀疏图,所以采用邻接表
的方式存储。
代码模板
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 typedef pair<int , int > PII;int n; int h[N], w[N], e[N], ne[N], idx; int dist[N]; bool st[N]; int dijkstra () { memset (dist, 0x3f , sizeof dist); dist[1 ] = 0 ; priority_queue<PII, vector<PII>, greater<PII>> heap; heap.push ({0 , 1 }); while (heap.size ()) { auto t = heap.top (); heap.pop (); int ver = t.second, distance = t.first; if (st[ver]) continue ; st[ver] = true ; for (int i = h[ver]; i != -1 ; i = ne[i]) { int j = e[i]; if (dist[j] > distance + w[i]) { dist[j] = distance + w[i]; heap.push ({dist[j], j}); } } } if (dist[n] == 0x3f3f3f3f ) return -1 ; return dist[n]; }
数学知识 数论 质数 Def 在大于1的整数中,如果只包含1和整数这两个约数,就被称为质数,或者素数。
质数的判定—试除法
实现思想 :i 从 2 枚举到 $\sqrt{n}$ 会缩减循环,但是sqrt(n)
执行较慢,所以不使用。O(sqrt(n))
1 2 3 4 5 6 7 8 bool is_prime (int x) { if (x < 2 ) return false ; for (int i = 2 ; i <= x / i; i ++ ) if (x % i == 0 ) return false ; return true ; }
分解质因数—试除法
实现思想:从小到大枚举所有数,O(sqrt(n))
。
1 2 3 4 5 6 7 8 9 10 11 12 void divide (int x) { for (int i = 2 ; i <= x / i; i ++ ) if (x % i == 0 ) { int s = 0 ; while (x % i == 0 ) x /= i, s ++ ; cout << i << ' ' << s << endl; } if (x > 1 ) cout << x << ' ' << 1 << endl; cout << endl; }
朴素筛法求素数—埃氏筛法
实现思想:从2遍历到n,遍历到第i个数的时候,若该数没有被删除,则加入质数数组,然后删除该数的所有倍数。O(n * loglogn)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 int primes[N], cnt; bool st[N]; void get_primes (int n) { for (int i = 2 ; i <= n; i ++ ) { if (st[i]) continue ; primes[cnt ++ ] = i; for (int j = i + i; j <= n; j += i) st[j] = true ; } }
线性筛法求素数
相较于埃氏筛法
快将近一倍。
实现思想:n只会被它的最小质因子筛掉。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 int primes[N], cnt; bool st[N]; void get_primes (int n) { for (int i = 2 ; i <= n; i ++ ) { if (!st[i]) primes[cnt ++ ] = i; for (int j = 0 ; primes[j] <= n / i; j ++ ) { st[primes[j] * i] = true ; if (i % primes[j] == 0 ) break ; } } }
约数 试除法求所有约数
实现思想类似于试除法求质数。O(sqrt(n))
1 2 3 4 5 6 7 8 9 10 11 12 vector<int > get_divisors (int x) { vector<int > res; for (int i = 1 ; i <= x / i; i ++ ) if (x % i == 0 ) { res.push_back (i); if (i != x / i) res.push_back (x / i); } sort (res.begin (), res.end ()); return res; }
约数个数
1 2 3 如果 N = p1^ c1 * p2^ c2 * ... *pk^ ck 约数个数: (c1 + 1) * (c2 + 1) * ... * (ck + 1) 约数之和: (p1^ 0 + p1^ 1 + ... + p1^ c1) * ... * (pk^ 0 + pk^ 1 + ... + pk^ ck)
组合计数 高斯消元 简单博弈论