# 算法代码模板# 第一章 基础算法# 快速排序void quick_sort ( int q[ ] , int l, int r) { if ( l >= r) return ; int i = l - 1 , j = r + 1 , x = q[ l + r >> 1 ] ; while ( i < j) { do i ++ ; while ( q[ i] < x) ; do j -- ; while ( q[ j] > x) ; if ( i < j) swap ( q[ i] , q[ j] ) ; } quick_sort ( q, l, j) , quick_sort ( q, j + 1 , r) ; }
# 归并排序void merge_sort ( int q[ ] , int l, int r) { if ( l >= r) return ; int mid = l + r >> 1 ; merge_sort ( q, l, mid) ; merge_sort ( q, mid + 1 , r) ; int k = 0 , i = l, j = mid + 1 ; while ( i <= mid && j <= r) if ( q[ i] <= q[ j] ) tmp[ k ++ ] = q[ i ++ ] ; else tmp[ k ++ ] = q[ j ++ ] ; while ( i <= mid) tmp[ k ++ ] = q[ i ++ ] ; while ( j <= r) tmp[ k ++ ] = q[ j ++ ] ; for ( i = l, j = 0 ; i <= r; i ++ , j ++ ) q[ i] = tmp[ j] ; }
# 整数二分算法bool check ( int x) { } int bsearch_1 ( int l, int r) { while ( l < r) { int mid = l + r >> 1 ; if ( check ( mid) ) r = mid; else l = mid + 1 ; } return l; } int bsearch_2 ( int l, int r) { while ( l < r) { int mid = l + r + 1 >> 1 ; if ( check ( mid) ) l = mid; else r = mid - 1 ; } return l; }
# 浮点数二分算法bool check ( double x) { } double bsearch_3 ( double l, double r) { const double eps = 1e-6 ; while ( r - l > eps) { double mid = ( l + r) / 2 ; if ( check ( mid) ) r = mid; else l = mid; } return l; }
# 高精度加法vector< int > add ( vector< int > & A, vector< int > & B) { if ( A. size ( ) < B. 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; }
public static List < Integer > add ( List < Integer > A , List < Integer > B ) { if ( A . size ( ) < B . size ( ) ) { return add ( B , A ) ; } List < Integer > C = new ArrayList < Integer > ( ) ; int temp = 0 ; for ( int i = 0 ; i < A . size ( ) ; i++ ) { temp += A . get ( i) ; if ( i < B . size ( ) ) { temp += B . get ( i) ; } C . add ( temp % 10 ) ; temp /= 10 ; } if ( temp != 0 ) { C . add ( temp) ; } Collections . reverse ( C ) ; return C ; } public static List < Integer > getList ( int num) { List < Integer > list = new ArrayList < > ( ) ; while ( num > 0 ) { list. add ( num % 10 ) ; num /= 10 ; } return list; } public static void main ( String args[ ] ) { int a = 123456 ; int b = 654 ; List < Integer > A = getList ( a) ; List < Integer > B = getList ( b) ; List < Integer > C = add ( A , B ) ; }
# 高精度减法vector< int > sub ( vector< int > & A, vector< int > & B) { vector< int > C; for ( int i = 0 , t = 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; }
public static List < Integer > sub ( List < Integer > A , List < Integer > B ) { List < Integer > C = new ArrayList < > ( ) ; for ( int i = 0 , temp = 0 ; i < A . size ( ) ; i++ ) { temp = A . get ( i) - temp; if ( i < B . size ( ) ) { temp -= B . get ( i) ; } C . add ( ( temp + 10 ) % 10 ) ; temp = temp < 0 ? 1 : 0 ; } while ( C . size ( ) > 1 && C . get ( C . size ( ) - 1 ) == 0 ) { C . remove ( C . size ( ) - 1 ) ; } Collections . reverse ( C ) ; return C ; } public static List < Integer > getList ( int num) { List < Integer > list = new ArrayList < > ( ) ; while ( num > 0 ) { list. add ( num % 10 ) ; num /= 10 ; } return list; } public static void main ( String args[ ] ) { int a = 123456 ; int b = 654 ; List < Integer > A = getList ( a) ; List < Integer > B = getList ( b) ; List < Integer > C = sub ( A , B ) ; }
# 高精度乘低精度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; }
public static List < Integer > mul ( List < Integer > A , int b) { List < Integer > C = new ArrayList < > ( ) ; int temp = 0 ; for ( int i = 0 ; i < A . size ( ) || temp != 0 ; i++ ) { if ( i < A . size ( ) ) { temp += A . get ( i) * b; } C . add ( temp % 10 ) ; temp /= 10 ; } while ( C . size ( ) > 1 && C . get ( C . size ( ) - 1 ) == 0 ) { C . remove ( C . size ( ) - 1 ) ; } Collections . reverse ( C ) ; return C ; } public static List < Integer > getList ( int num) { List < Integer > list = new ArrayList < > ( ) ; while ( num > 0 ) { list. add ( num % 10 ) ; num /= 10 ; } return list; } public static void main ( String args[ ] ) { int a = 123456 ; int b = 654 ; List < Integer > A = getList ( a) ; List < Integer > B = getList ( b) ; List < Integer > C = mul ( A , 654 ) ; }
# 高精度除以低精度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; }
public static List < Integer > div ( List < Integer > A , int b, int r) { List < Integer > C = new ArrayList < > ( ) ; r = 0 ; for ( int i = A . size ( ) - 1 ; i >= 0 ; i-- ) { r = r * 10 + A . get ( i) ; C . add ( r / b) ; r %= b; } Collections . reverse ( C ) ; while ( C . size ( ) > 1 && C . get ( C . size ( ) - 1 ) == 0 ) { C . remove ( C . size ( ) - 1 ) ; } Collections . reverse ( C ) ; return C ; } public static List < Integer > getList ( int num) { List < Integer > list = new ArrayList < > ( ) ; while ( num > 0 ) { list. add ( num % 10 ) ; num /= 10 ; } return list; } public static void main ( String args[ ] ) { int a = 123456 ; int b = 654 ; List < Integer > A = getList ( a) ; List < Integer > B = getList ( b) ; List < Integer > C = div ( A , 654 , 0 ) ; }
# 一维前缀和S[ i] = a[ 1 ] + a[ 2 ] + . . . a[ i] a[ l] + . . . + a[ r] = S[ r] - S[ l - 1 ]
# 二维前缀和S[ i, j] = 第i行j列格子左上部分所有元素的和 以( x1, y1) 为左上角,( x2, y2) 为右下角的子矩阵的和为: S[ x2, y2] - S[ x1 - 1 , y2] - S[ x2, y1 - 1 ] + S[ x1 - 1 , y1 - 1 ]
# 一维差分给区间[ l, r] 中的每个数加上c:B[ l] += c, B[ r + 1 ] -= c
# 二维差分给以( x1, y1) 为左上角,( x2, y2) 为右下角的子矩阵中的所有元素加上c: S[ x1, y1] += c, S[ x2 + 1 , y1] -= c, S[ x1, y2 + 1 ] -= c, S[ x2 + 1 , y2 + 1 ] += c
# 位运算求n的第k位数字: n >> k & 1 返回n的最后一位1 :lowbit ( n) = n & - n
# 双指针算法for ( int i = 0 , j = 0 ; i < n; i ++ ) { while ( j < i && check ( i, j) ) j ++ ; } 常见问题分类: ( 1 ) 对于一个序列,用两个指针维护一段区间 ( 2 ) 对于两个序列,维护某种次序,比如归并排序中合并两个有序序列的操作
# 离散化vector< int > alls; sort ( alls. begin ( ) , alls. end ( ) ) ; alls. erase ( unique ( alls. begin ( ) , alls. end ( ) ) , alls. end ( ) ) ; int find ( int x) { int l = 0 , r = alls. size ( ) - 1 ; while ( l < r) { int mid = l + r >> 1 ; if ( alls[ mid] >= x) r = mid; else l = mid + 1 ; } return r + 1 ; }
# 区间合并void merge ( vector< PII> & segs) { vector< PII> res; sort ( segs. begin ( ) , segs. end ( ) ) ; int st = - 2e9 , ed = - 2e9 ; for ( auto seg : segs) if ( ed < seg. first) { if ( st != - 2e9 ) res. push_back ( { st, ed} ) ; st = seg. first, ed = seg. second; } else ed = max ( ed, seg. second) ; if ( st != - 2e9 ) res. push_back ( { st, ed} ) ; segs = res; }
# 第二章 数据结构# 单链表int head, e[ N] , ne[ N] , idx; void init ( ) { head = - 1 ; idx = 0 ; } void insert ( int a) { e[ idx] = a, ne[ idx] = head, head = idx ++ ; } void remove ( ) { head = ne[ head] ; }
# 双链表int e[ N] , l[ N] , r[ N] , idx; void init ( ) { r[ 0 ] = 1 , l[ 1 ] = 0 ; idx = 2 ; } void insert ( int a, int x) { e[ idx] = x; l[ idx] = a, r[ idx] = r[ a] ; l[ r[ a] ] = idx, r[ a] = idx ++ ; } void remove ( int a) { l[ r[ a] ] = l[ a] ; r[ l[ a] ] = r[ a] ; }
int stk[ N] , tt = 0 ; stk[ ++ tt] = x; tt -- ; stk[ tt] ; if ( tt > 0 ) { }
# 普通队列int q[ N] , hh = 0 , tt = - 1 ; q[ ++ tt] = x; hh ++ ; q[ hh] ; if ( hh <= tt) { }
# 循环队列int q[ N] , hh = 0 , tt = 0 ; q[ tt ++ ] = x; if ( tt == N) tt = 0 ; hh ++ ; if ( hh == N) hh = 0 ; q[ hh] ; if ( hh != tt) { }
# 单调栈常见模型:找出每个数左边离它最近的比它大/ 小的数 int tt = 0 ; for ( int i = 1 ; i <= n; i ++ ) { while ( tt && check ( stk[ tt] , i) ) tt -- ; stk[ ++ tt] = i; }
# 单调队列常见模型:找出滑动窗口中的最大值/ 最小值 int hh = 0 , tt = - 1 ; for ( int i = 0 ; i < n; i ++ ) { while ( hh <= tt && check_out ( q[ hh] ) ) hh ++ ; while ( hh <= tt && check ( q[ tt] , i) ) tt -- ; q[ ++ tt] = i; }
# KMP求模式串的Next数组: for ( int i = 2 , j = 0 ; i <= m; i ++ ) { while ( j && p[ i] != p[ j + 1 ] ) j = ne[ j] ; if ( p[ i] == p[ j + 1 ] ) j ++ ; ne[ i] = j; } 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 = ne[ j] ; } }
# Trie 树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] ; }
# 朴素并查集int p[ N] ; int find ( int x) { if ( p[ x] != x) p[ x] = find ( p[ x] ) ; return p[ x] ; } for ( int i = 1 ; i <= n; i ++ ) p[ i] = i; p[ find ( a) ] = find ( b) ;
# 维护 size 的并查集int p[ N] , size[ N] ; int find ( int x) { if ( p[ x] != x) p[ x] = find ( p[ x] ) ; return p[ x] ; } for ( int i = 1 ; i <= n; i ++ ) { p[ i] = i; size[ i] = 1 ; } size[ find ( b) ] += size[ find ( a) ] ; p[ find ( a) ] = find ( b) ;
# 维护到祖宗节点距离的并查集int p[ N] , d[ N] ; int find ( int x) { if ( p[ x] != x) { int u = find ( p[ x] ) ; d[ x] += d[ p[ x] ] ; p[ x] = u; } return p[ x] ; } for ( int i = 1 ; i <= n; i ++ ) { p[ i] = i; d[ i] = 0 ; } p[ find ( a) ] = find ( b) ; d[ find ( a) ] = distance;
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) ;
# 一般哈希:拉链法int h[ N] , e[ N] , ne[ N] , idx; void insert ( int x) { int k = ( x % N + N) % N; e[ idx] = x; ne[ idx] = h[ k] ; h[ k] = idx ++ ; } bool find ( int x) { int k = ( x % N + N) % N; for ( int i = h[ k] ; i != - 1 ; i = ne[ i] ) if ( e[ i] == x) return true ; return false ; }
# 一般哈希:开放寻址法int h[ N] ; int find ( int x) { int t = ( x % N + N) % N; while ( h[ t] != null && h[ t] != x) { t ++ ; if ( t == N) t = 0 ; } return t; }
# 字符串哈希核心思想:将字符串看成 P 进制数,P 的经验值是 131 或 13331,取这两个值的冲突概率低 小技巧:取模的数用 2^64,这样直接用 unsigned long long 存储,溢出的结果就是取模的结果
typedef unsigned long long ULL; ULL h[ N] , p[ N] ; p[ 0 ] = 1 ; for ( int i = 1 ; i <= n; i ++ ) { h[ i] = h[ i - 1 ] * P + str[ i] ; p[ i] = p[ i - 1 ] * P; } ULL get ( int l, int r) { return h[ r] - h[ l - 1 ] * p[ r - l + 1 ] ; }
# c++ STLvector, 变长数组,倍增的思想 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位取反
# 第三章 搜索与图论# 树与图的存储 树是一种特殊的图,与图的存储方式相同。
对于无向图中的边 ab,存储两条有向边 a->b, b->a。 因此我们可以只考虑有向图的存储。
(1) 邻接矩阵:g [a][b] 存储边 a->b
(2) 邻接表
# 邻接表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
表示边数
# 深度优先遍历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) ; } }
# 宽度优先遍历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
表示边数
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 ; }
# 朴素 dijkstra 算法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 时间复杂度 O(mlogn)
, n
表示点数, m
表示边数
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] ; }
# Bellman-Ford 算法 时间复杂度 O(nm)
, n
表示点数, m
表示边数。注意在模板题中需要对下面的模板稍作修改,加上备份数组,详情见模板题。
int n, m; int dist[ N] ; struct Edge { int a, b, w; } edges[ M] ; int bellman_ford ( ) { memset ( dist, 0x3f , sizeof dist) ; dist[ 1 ] = 0 ; for ( int i = 0 ; i < n; i ++ ) { for ( int j = 0 ; j < m; j ++ ) { int a = edges[ j] . a, b = edges[ j] . b, w = edges[ j] . w; if ( dist[ b] > dist[ a] + w) dist[ b] = dist[ a] + w; } } if ( dist[ n] > 0x3f3f3f3f / 2 ) return - 1 ; return dist[ n] ; }
# spfa 算法(队列优化的 Bellman-Ford 算法) 时间复杂度 平均情况下 O(m)
,最坏情况下 O (nm),n 表示点数,m 表示边数
int n; int h[ N] , w[ N] , e[ N] , ne[ N] , idx; int dist[ N] ; bool st[ N] ; int spfa ( ) { memset ( dist, 0x3f , sizeof dist) ; dist[ 1 ] = 0 ; queue< int > q; q. push ( 1 ) ; st[ 1 ] = true ; while ( q. size ( ) ) { auto t = q. front ( ) ; q. pop ( ) ; st[ t] = false ; for ( int i = h[ t] ; i != - 1 ; i = ne[ i] ) { int j = e[ i] ; if ( dist[ j] > dist[ t] + w[ i] ) { dist[ j] = dist[ t] + w[ i] ; if ( ! st[ j] ) { q. push ( j) ; st[ j] = true ; } } } } if ( dist[ n] == 0x3f3f3f3f ) return - 1 ; return dist[ n] ; }
# spfa 判断图中是否存在负环 时间复杂度是 O(nm)
, n
表示点数, m
表示边数
int n; int h[ N] , w[ N] , e[ N] , ne[ N] , idx; int dist[ N] , cnt[ N] ; bool st[ N] ; bool spfa ( ) { queue< int > q; for ( int i = 1 ; i <= n; i ++ ) { q. push ( i) ; st[ i] = true ; } while ( q. size ( ) ) { auto t = q. front ( ) ; q. pop ( ) ; st[ t] = false ; for ( int i = h[ t] ; i != - 1 ; i = ne[ i] ) { int j = e[ i] ; if ( dist[ j] > dist[ t] + w[ i] ) { dist[ j] = dist[ t] + w[ i] ; cnt[ j] = cnt[ t] + 1 ; if ( cnt[ j] >= n) return true ; if ( ! st[ j] ) { q. push ( j) ; st[ j] = true ; } } } } return false ; }
# floyd 算法 时间复杂度是 O(n3)
, n
表示点数
初始化: for ( int i = 1 ; i <= n; i ++ ) for ( int j = 1 ; j <= n; j ++ ) if ( i == j) d[ i] [ j] = 0 ; else d[ i] [ j] = INF; void floyd ( ) { for ( int k = 1 ; k <= n; k ++ ) for ( int i = 1 ; i <= n; i ++ ) for ( int j = 1 ; j <= n; j ++ ) d[ i] [ j] = min ( d[ i] [ j] , d[ i] [ k] + d[ k] [ j] ) ; }
# 朴素版 prim 算法 时间复杂度是 O(n2+m)
, n
表示点数, m
表示边数
int n; int g[ N] [ N] ; int dist[ N] ; bool st[ N] ; int prim ( ) { memset ( dist, 0x3f , sizeof dist) ; int res = 0 ; for ( int i = 0 ; i < n; i ++ ) { int t = - 1 ; for ( int j = 1 ; j <= n; j ++ ) if ( ! st[ j] && ( t == - 1 || dist[ t] > dist[ j] ) ) t = j; if ( i && dist[ t] == INF) return INF; if ( i) res += dist[ t] ; st[ t] = true ; for ( int j = 1 ; j <= n; j ++ ) dist[ j] = min ( dist[ j] , g[ t] [ j] ) ; } return res; }
# Kruskal 算法 时间复杂度是 O(mlogm)
, n
表示点数, m
表示边数
int n, m; int p[ N] ; struct Edge { int a, b, w; bool operator < ( const Edge & W) const { return w < W. w; } } edges[ M] ; int find ( int x) { if ( p[ x] != x) p[ x] = find ( p[ x] ) ; return p[ x] ; } int kruskal ( ) { sort ( edges, edges + m) ; for ( int i = 1 ; i <= n; i ++ ) p[ i] = i; int res = 0 , cnt = 0 ; for ( int i = 0 ; i < m; i ++ ) { int a = edges[ i] . a, b = edges[ i] . b, w = edges[ i] . w; a = find ( a) , b = find ( b) ; if ( a != b) { p[ a] = b; res += w; cnt ++ ; } } if ( cnt < n - 1 ) return INF; return res; }
# 染色法判别二分图 时间复杂度是 O(n+m)
, n
表示点数, m
表示边数
int n; int h[ N] , e[ M] , ne[ M] , idx; int color[ N] ; bool dfs ( int u, int c) { color[ u] = c; for ( int i = h[ u] ; i != - 1 ; i = ne[ i] ) { int j = e[ i] ; if ( color[ j] == - 1 ) { if ( ! dfs ( j, ! c) ) return false ; } else if ( color[ j] == c) return false ; } return true ; } bool check ( ) { memset ( color, - 1 , sizeof color) ; bool flag = true ; for ( int i = 1 ; i <= n; i ++ ) if ( color[ i] == - 1 ) if ( ! dfs ( i, 0 ) ) { flag = false ; break ; } return flag; }
# 匈牙利算法 时间复杂度是 O(nm)
, n
表示点数, m
表示边数
int n1, n2; int h[ N] , e[ M] , ne[ M] , idx; int match[ N] ; bool st[ N] ; bool find ( int x) { for ( int i = h[ x] ; i != - 1 ; i = ne[ i] ) { int j = e[ i] ; if ( ! st[ j] ) { st[ j] = true ; if ( match[ j] == 0 || find ( match[ j] ) ) { match[ j] = x; return true ; } } } return false ; } int res = 0 ; for ( int i = 1 ; i <= n1; i ++ ) { memset ( st, false , sizeof st) ; if ( find ( i) ) res ++ ; }
# 第四章 数学知识# 试除法判定质数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 ; }
# 试除法分解质因数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; }
# 朴素筛法求素数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 ; } }
# 线性筛法求素数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 ; } } }
# 试除法求所有约数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; }
# 约数个数和约数之和如果 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)
# 欧几里得算法int gcd ( int a, int b) { return b ? gcd ( b, a % b) : a; }
# 求欧拉函数 表示小于或等于 N 的正整数中与 N 互质的数的个数
int phi ( int x) { int res = x; for ( int i = 2 ; i <= x / i; i ++ ) if ( x % i == 0 ) { res = res / i * ( i - 1 ) ; while ( x % i == 0 ) x /= i; } if ( x > 1 ) res = res / x * ( x - 1 ) ; return res; }
# 筛法求欧拉函数int primes[ N] , cnt; int euler[ N] ; bool st[ N] ; void get_eulers ( int n) { euler[ 1 ] = 1 ; for ( int i = 2 ; i <= n; i ++ ) { if ( ! st[ i] ) { primes[ cnt ++ ] = i; euler[ i] = i - 1 ; } for ( int j = 0 ; primes[ j] <= n / i; j ++ ) { int t = primes[ j] * i; st[ t] = true ; if ( i % primes[ j] == 0 ) { euler[ t] = euler[ i] * primes[ j] ; break ; } euler[ t] = euler[ i] * ( primes[ j] - 1 ) ; } } }
# 快速幂求 m^ k mod p,时间复杂度 O ( logk) 。 int qmi ( int m, int k, int p) { int res = 1 % p, t = m; while ( k) { if ( k& 1 ) res = res * t % p; t = t * t % p; k >>= 1 ; } return res; }
# 扩展欧几里得算法int exgcd ( int a, int b, int & x, int & y) { if ( ! b) { x = 1 ; y = 0 ; return a; } int d = exgcd ( b, a % b, y, x) ; y -= ( a/ b) * x; return d; }
# 高斯消元int gauss ( ) { int c, r; for ( c = 0 , r = 0 ; c < n; c ++ ) { int t = r; for ( int i = r; i < n; i ++ ) if ( fabs ( a[ i] [ c] ) > fabs ( a[ t] [ c] ) ) t = i; if ( fabs ( a[ t] [ c] ) < eps) continue ; for ( int i = c; i <= n; i ++ ) swap ( a[ t] [ i] , a[ r] [ i] ) ; for ( int i = n; i >= c; i -- ) a[ r] [ i] /= a[ r] [ c] ; for ( int i = r + 1 ; i < n; i ++ ) if ( fabs ( a[ i] [ c] ) > eps) for ( int j = n; j >= c; j -- ) a[ i] [ j] -= a[ r] [ j] * a[ i] [ c] ; r ++ ; } if ( r < n) { for ( int i = r; i < n; i ++ ) if ( fabs ( a[ i] [ n] ) > eps) return 2 ; return 1 ; } for ( int i = n - 1 ; i >= 0 ; i -- ) for ( int j = i + 1 ; j < n; j ++ ) a[ i] [ n] -= a[ i] [ j] * a[ j] [ n] ; return 0 ; }
# 递归法求组合数for ( int i = 0 ; i < N; i ++ ) for ( int j = 0 ; j <= i; j ++ ) if ( ! j) c[ i] [ j] = 1 ; else c[ i] [ j] = ( c[ i - 1 ] [ j] + c[ i - 1 ] [ j - 1 ] ) % mod;
# 通过预处理逆元的方式求组合数首先预处理出所有阶乘取模的余数 fact [N],以及所有阶乘取模的逆元 infact [N] 如果取模的数是质数,可以用费马小定理求逆元
int qmi ( int a, int k, int p) { int res = 1 ; while ( k) { if ( k & 1 ) res = ( LL) res * a % p; a = ( LL) a * a % p; k >>= 1 ; } return res; } fact[ 0 ] = infact[ 0 ] = 1 ; for ( int i = 1 ; i < N; i ++ ) { fact[ i] = ( LL) fact[ i - 1 ] * i % mod; infact[ i] = ( LL) infact[ i - 1 ] * qmi ( i, mod - 2 , mod) % mod; }
# Lucas 定理若 p 是质数,则对于任意整数 1 <= m <= n,有: C(n, m) = C(n % p, m % p) * C(n / p, m / p) (mod p)
int qmi ( int a, int k, int p) { int res = 1 % p; while ( k) { if ( k & 1 ) res = ( LL) res * a % p; a = ( LL) a * a % p; k >>= 1 ; } return res; } int C ( int a, int b, int p) { if ( a < b) return 0 ; LL x = 1 , y = 1 ; for ( int i = a, j = 1 ; j <= b; i -- , j ++ ) { x = ( LL) x * i % p; y = ( LL) y * j % p; } return x * ( LL) qmi ( y, p - 2 , p) % p; } int lucas ( LL a, LL b, int p) { if ( a < p && b < p) return C ( a, b, p) ; return ( LL) C ( a % p, b % p, p) * lucas ( a / p, b / p, p) % p; }
# 分解质因数法求组合数当我们需要求出组合数的真实值,而非对某个数的余数时,分解质因数的方式比较好用: 1. 筛法求出范围内的所有质数 2. 通过 C (a, b) = a! /b! / (a - b)! 这个公式求出每个质因子的次数。 n! 中 p 的次数是 n /p + n /p^2 + n /p^3 + ... 3. 用高精度乘法将所有质因子相乘
int primes[ N] , cnt; int sum[ N] ; 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 ; } } } int get ( int n, int p) { int res = 0 ; while ( n) { res += n / p; n /= p; } return res; } vector< int > mul ( vector< int > a, int b) { vector< int > c; int t = 0 ; for ( int i = 0 ; i < a. size ( ) ; i ++ ) { t += a[ i] * b; c. push_back ( t % 10 ) ; t /= 10 ; } while ( t) { c. push_back ( t % 10 ) ; t /= 10 ; } return c; } get_primes ( a) ; for ( int i = 0 ; i < cnt; i ++ ) { int p = primes[ i] ; sum[ i] = get ( a, p) - get ( b, p) - get ( a - b, p) ; } vector< int > res; res. push_back ( 1 ) ; for ( int i = 0 ; i < cnt; i ++ ) for ( int j = 0 ; j < sum[ i] ; j ++ ) res = mul ( res, primes[ i] ) ;
# 卡特兰数给定n个0 和n个1 ,它们按照某种顺序排成长度为2 n的序列,满足任意前缀中0 的个数都不少于1 的个数的序列的数量为: Cat ( n) = C ( 2 n, n) / ( n + 1 )