第一章 基础算法
高精度 算法4:高精度(只有C++需要),一般有四种情况:
两个大整数相加 A + B,A、B的位数 $\le 10^6$;
两个大整数相减 A - B,A、B的位数 $\le 10^6$;
一个大整数乘以一个小整数 A * a,A的位数$\le 10^6$,a$\le 10000$;
一个大整数除以一个小整数 A / a,A的位数$\le 10^6$,a$\le 10000$;
首先要考虑的是一个大整数如何存储?方法是可以将其中的每一位保存在一个数组中,为了方便运算,让a[0]
存数字的个位,a[1]
存数字的十位……依次存高位。如数字以string
类型输入a = "123456"
,用vector<int>
来存储,A=[6,5,4,3,2,1]
。
(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 28 29 30 31 32 33 34 35 36 37 38 #include <iostream> #include <vector> using namespace std ;vector <int > add(vector <int > &A, vector <int > &B){ vector <int > C; if (A.size () < B.size ()) return add(B, A); 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; } int main () { string a, b; vector <int > A, B; cin >> a >> b; for (int i = a.size () - 1 ; i >= 0 ; i--) A.push_back(a[i] - '0' ); for (int i = b.size () - 1 ; i >= 0 ; i--) B.push_back(b[i] - '0' ); vector <int > C = add(A, B); for (int i = C.size () - 1 ; i >= 0 ; i--) printf ("%d" , C[i]); return 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 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 #include <iostream> #include <vector> using namespace std ;const int base = 1000000000 ;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 % base); t /= base; } if (t) C.push_back(t); return C; } int main () { string a, b; vector <int > A, B; cin >> a >> b; for (int i = a.size () - 1 , s = 0 , j = 0 , t = 1 ; i >= 0 ; i -- ) { s += (a[i] - '0' ) * t; j ++, t *= 10 ; if (j == 9 || i == 0 ) { A.push_back(s); s = j = 0 ; t = 1 ; } } for (int i = b.size () - 1 , s = 0 , j = 0 , t = 1 ; i >= 0 ; i -- ) { s += (b[i] - '0' ) * t; j ++, t *= 10 ; if (j == 9 || i == 0 ) { B.push_back(s); s = j = 0 ; t = 1 ; } } auto C = add(A, B); cout << C.back(); for (int i = C.size () - 2 ; i >= 0 ; i -- ) printf ("%09d" , C[i]); cout << endl ; return 0 ; }
(2)减法
两个数组的减法运算也是来模拟人工手算的过程,从个位开始逐位相减,不够就向前一位借1,加10。
算法的思路:
如果A$\ge$B,就计算$A - B$;如果A$<$B,就计算 $-(B - A)$。
在每一位上,若$A_i-B_i-t \ge 0$,就计算$A_i-B_i-t$;若$A_i-B_i-t < 0$,就计算$A_i-B_i+10-t $,其中$t$代表借位。
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 #include <iostream> #include <vector> using namespace std ;bool cmp (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; } int main () { string a, b; vector <int > A, B; cin >> a >> b; for (int i = a.size () - 1 ; i >= 0 ; i--) A.push_back(a[i] - '0' ); for (int i = b.size () - 1 ; i >= 0 ; i--) B.push_back(b[i] - '0' ); if (cmp(A, B)) { vector <int > C = sub(A, B); for (int i = C.size () - 1 ; i >= 0 ; i--) printf ("%d" , C[i]); } else { vector <int > C = sub(B, A); printf ("-" ); for (int i = C.size () - 1 ; i >= 0 ; i--) printf ("%d" , C[i]); } return 0 ; }
(3)乘法
也是模拟手算乘法,区别是:逐位相乘时是乘以整个被乘数b。
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 #include <iostream> #include <vector> using namespace std ;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; } int main () { string a; int b; cin >> a >> b; vector <int > A; for (int i = a.size () - 1 ; i >= 0 ; i--) A.push_back(a[i] - '0' ); auto C = mul(A, b); for (int i = C.size () - 1 ; i >= 0 ; i--) printf ("%d" , C[i]); return 0 ; }
(4)除法
同样的思路,模拟手算的过程,注意区别:除法是从最高位开始除的。
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> #include <vector> #include <algorithm> using namespace std ;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; } int main () { string a; int b; cin >> a >> b; vector <int > A; for (int i = a.size () - 1 ; i >= 0 ; i--) A.push_back(a[i] - '0' ); int r; auto C = div(A, b, r); for (int i = C.size () - 1 ; i >= 0 ; i--) printf ("%d" ,C[i]); cout << endl << r << endl ; return 0 ; }
前缀和,差分 算法5:前缀和,差分
前缀和:设一个数组$a_1,a_2,a_3,\dots,a_n$,(注意下标从1开始),定义其前缀和为$S_i=a_1+a_2+\dots+a_i$,规定$S_0=0$。
如何求前缀和$S_i$:for
遍历即可;
前缀和的作用:可以方便地求出序列中某一段的和,如求下标区间$[l,r]$内的元素的和,即可用$S_r-S_{l-1}$,时间复杂度为$O(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 #include <iostream> using namespace std ;const int N = 100010 ;int n, m;int a[N], s[N];int main () { scanf ("%d%d" , &n, &m); for (int i = 1 ; i <= n; i++) scanf ("%d" , &a[i]); for (int i = 1 ; i <= n; i++) s[i] = s[i - 1 ] + a[i]; while (m--) { int l, r; scanf ("%d%d" , &l, &r); printf ("%d\n" , s[r] - s[l - 1 ]); } return 0 ; }
前缀和也可以扩展到二维,求区间和$\to$求子矩阵和。用$S_{ij}$表示左上角的子矩阵的和。如下图若要求以$(x_1,y_1)$为左上角,以$(x_2,y_2)$为右下角的子矩阵的和,那就可以转化成求:
如何求子矩阵和$S_{ij}$:两层for
遍历i
,j
,
主要的思想就是容斥原理 。
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> using namespace std ;const int N = 1010 ;int n, m, q;int a[N][N], s[N][N];int main () { scanf ("%d%d%d" , &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 - 1 ][j] + s[i][j - 1 ] - 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[x1 - 1 ][y2] - s[x2][y1 - 1 ] + s[x1 - 1 ][y1 - 1 ]); } return 0 ; }
差分是前缀和的逆运算 ,设一个数组$a_1, a_2, \dots, a_n$,现构造一个数组$b_1,b_2,\dots, b_n$,使得$a_i=b_1+b_2+\dots+b_n$,即$a$数组的元素是$b$数组的前缀和,$b$数组的元素是$a$数组的差分 。则有:
差分的作用:
若有$b$数组,就可以通过求前缀和的方法求得原数组$a$,时间复杂度$O(n)$。
若要对$a$数组下标为$[l,r]$区间的一段元素都加上$c$,则要$O(n)$的时间复杂度;若考虑改动$b$数组,那只要改变两个元素,即让$b_l+c$,让$b_{r+1}-c$,则只要$O(1)$的时间复杂度,这样由数组$b$得到的数组$a$的下标为$[l,r]$的一段就都加上了$c$。
那若有了数组$a$,如何得到数组$b$:可以假设数组$a$初始全部是0,依次在区间[1,1]加上$a_1$,在区间[2,2]加上$a_2$,……,在区间[n,n]加上$a_n$,即转换到对数组$b$的操作。
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 ;int n, m;int a[N], b[N];void insert (int l, int r, int c) { b[l] += c; b[r + 1 ] -= c; } int main () { scanf ("%d%d" , &n, &m); for (int i = 1 ; i <= n; i++) { scanf ("%d" , &a[i]); insert(i, i, a[i]); } while (m--) { int l, r, c; scanf ("%d%d%d" , &l, &r, &c); insert(l, r, c); } for (int i = 1 ; i <= n; i++) { b[i] += b[i-1 ]; printf ("%d " , b[i]); } return 0 ; }
差分也有二维的形式,原矩阵元素$a_{ij}$,差分矩阵元素$b_{ij}$,使得$a_{ij}$是差分矩阵$b_{ij}$的前缀和。
其作用也可由上面的一维差分类比过来:给矩阵$a$的某一个子矩阵(左上角为$(x_1,y_1)$,右下角为$(x_2,y_2)$)中的元素全都加上一个数$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 40 41 42 #include <iostream> using namespace std ;const int N = 1010 ;int n, m, q;int a[N][N], b[N][N];void insert (int x1, int y1, int x2, int y2, int c) { b[x1][y1] += c; b[x2 + 1 ][y1] -= c; b[x1][y2 + 1 ] -= c; b[x2 + 1 ][y2 + 1 ] += c; } int main () { scanf ("%d%d%d" , &n, &m, &q); for (int i = 1 ; i <= n; i++) for (int j = 1 ; j <= m; j++) { scanf ("%d" , &a[i][j]); insert(i, j, i, j, a[i][j]); } while (q--) { int x1, y1, x2, y2, c; scanf ("%d%d%d%d%d" , &x1, &y1, &x2, &y2, &c); insert(x1, y1, x2, y2, c); } for (int i = 1 ; i <= n; i++) { for (int j = 1 ; j <= m; j++) { b[i][j] += b[i-1 ][j] + b[i][j-1 ] - b[i-1 ][j-1 ]; printf ("%d " , b[i][j]); } printf ("\n" ); } return 0 ; }