快速排序
选取基准值(分界值)x,可以是数组的第一个元素,也可以是数组的最后一个元素,中间元素亦可。
使用双指针 i 和 j 从数组的两端开始扫描,从左端找到一个>=x 的值a,从右端找到一个<=x 的值b,交换ab的值,交换后i以及i之前的元素 都小于等于x,j以及j之后的元素 都大于等于x。交换后i应递增,j应递减,继续查找可以交换的值,知道ij相遇甚至错开(i > j)。
当数组成功划分两部分,左部分小于等于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 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 #include <iostream> using namespace std;const int N = 100010 ;int t[N];void quick_sort (int l, int r) { if (l >= r) return ; int i = l - 1 , j = r + 1 , x = t[(l + r) / 2 ]; while (i < j){ do i++; while (t[i] < x); do j--; while (t[j] > x); if (i < j) swap (t[i],t[j]); } quick_sort (l,j); quick_sort (j + 1 ,r); } int main () { int n; scanf ("%d" ,&n); for (int i = 0 ; i < n; i++) scanf ("%d" ,&t[i]); quick_sort (0 , n - 1 ); for (int i = 0 ; i < n; i++) printf ("%d " ,t[i]); return 0 ; }
归并排序
数组一分为二,递归调用归并排序。
利用双指针,合并一分为二的数组,将结果存到额外数组 tmp 中。
合并完成后,将结果数组覆盖至原数组中。
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 #include <iostream> using namespace std;const int N = 100010 ;int t[N], tmp[N];void merge_sort (int l, int r) { if (l >= r) return ; int mid = l + r >> 1 ; merge_sort (l, mid), merge_sort (mid + 1 , r); int k = 0 , i = l, j = mid + 1 ; while (i <= mid && j <= r){ if (t[i] <= t[j]) tmp[k++] = t[i++]; else tmp[k++] = t[j++]; } while (i <= mid) tmp[k++] = t[i++]; while (j <= r) tmp[k++] = t[j++]; for (int i = l, j = 0 ; i <= r; i++,j++) t[i] = tmp[j]; } int main () { int n; scanf ("%d" , &n); for (int i = 0 ; i < n; i++) scanf ("%d" , &t[i]); merge_sort (0 , n - 1 ); for (int i = 0 ; i < n; i++) printf ("%d " , t[i]); return 0 ; }
归并排序的应用——求解逆序对数量
对于任意给定的数字序列,举例如 4 3 2 1 ,若交换相邻元素,可以减少一个逆序对而不影响这两个元素于其它元素的逆序关系。此处交换 4 , 3 会得到 3 4 2 1 ,可以发现,前半部分元素与后半部分的逆序关系保持不变,前半部分内部消除了一个逆序对。
因此,我们可以考虑将数组一分为二,左右两部分分别消除内部逆序对,得到两个有序的序列,再消除序列之间的逆序对。若有两个有序序列为 2 4 6 和 1 3 5 ,使用双指针分别指向两个序列开头,将序列2的小元素移到大序列的前面,比如1移到2的前面,越过了2 4 6三个元素,那么就减少了三个逆序对。多次操作,直到大序列整体有序。
很显然,上述的操作就是归并排序的一种表现。将数组不断地一分为二,当一组只有两个元素的时候,对这一组一分为二成两个序列,每个序列一个元素,使用双指针归并后,表现为逆序的两个元素交换位置。同样长度为2的毗邻序列各自有序,同样可以使用双指针进行逆序对的消除。
因此,求解逆序对的数量,我们只需要在归并排序中,保存序列2元素越过的序列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 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 #include <iostream> using namespace std;const int N = 100010 ;long long t[N], tmp[N];long long counts = 0 ;void calculate_reverse_pair (int l, int r) { if (l >= r) return ; int mid = l + r >> 1 ; calculate_reverse_pair (l, mid), calculate_reverse_pair (mid + 1 , r); int k = 0 , i = l, j = mid + 1 ; while (i <= mid && j <= r){ if (t[i] <= t[j]) { tmp[k++] = t[i++]; } else { tmp[k++] = t[j++]; counts += (mid - i + 1 ); } } while (i <= mid) tmp[k++] = t[i++]; while (j <= r) tmp[k++] = t[j++]; for (int i = l, j = 0 ; i <= r; i++, j++) t[i] = tmp[j]; } int main () { int n; scanf ("%d" , &n); for (int i = 0 ; i < n; i++) scanf ("%lld" , &t[i]); calculate_reverse_pair (0 , n - 1 ); printf ("%lld" , counts); return 0 ; }
二分
二分就是寻找一个边界,将整个数组一分为二,一边满足性质,另一边不满足。
整数二分
AcWing—数的范围
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 #include <iostream> using namespace std;const int N = 100010 ;int t[N];int main () { int n, q; scanf ("%d%d" , &n, &q); for (int i = 0 ; i < n; i++) scanf ("%d" , &t[i]); while (q--){ int x; scanf ("%d" , &x); int l = 0 , r = n - 1 ; while (l < r){ int mid = l + r >> 1 ; if (t[mid] >= x) r = mid; else l = mid + 1 ; } if (t[l] != x) printf ("-1 -1\n" ); else { printf ("%d " , l); int l = 0 , r = n - 1 ; while (l < r){ int mid = l + r + 1 >> 1 ; if (t[mid] <= x) l = mid; else r = mid - 1 ; } printf ("%d\n" , l); } } return 0 ; }
浮点数二分
AcWing—三次方根
在求解次方根的问题中,需要注意,当x处于(0,1)的区间内时,其n次方根的结果会大于x。比如0.01的平方根是0.1。因此在使用二分求解n次方根的时候,我们需要将右边界加1。
二分结束的条件设置为一定的精度,比如当结果保留6位小数的时候,我们可以设置精度为1e-8,保留7位则1e-9,以此类推。确保结果的准确性。
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> using namespace std;int main () { double x; scanf ("%lf" , &x); bool aboveZero = true ; if (x < 0 ) aboveZero = false , x = 0 - x; double l = 0 , r = x + 1 ; while (r - l > 1e-8 ){ double mid = (l + r) / 2 ; if (mid*mid*mid >= x) r = mid; else l = mid; } printf ("%lf" , !aboveZero?-l:l); 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 #include <iostream> #include <vector> using namespace std;vector<int > add (vector<int > &A, vector<int > &B) { vector<int > C; int t = 0 ; for (int i = 0 ; t != 0 || i < A.size () || i < B.size (); i++){ if (i < A.size ()) t += A[i]; if (i < B.size ()) t += B[i]; C.push_back (t % 10 ); t = t >= 10 ? 1 : 0 ; } 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' ); auto C = add (A, B); while (C.size () != 0 ){ cout << C.back (); C.pop_back (); } 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 #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 true ; if (A[i] < B[i]) return false ; } return true ; } vector<int > sub (vector<int > &A, vector<int > &B) { vector<int > C; int t = 0 ; for (int i = 0 ; t != 0 || i < A.size () || i < B.size (); i++){ if (i < A.size ()) t = A[i] - t; if (i < B.size ()) t -= B[i]; C.push_back ((t + 10 ) % 10 ); t = t < 0 ? 1 : 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' ); bool a_greater = cmp (A, B); auto C = a_greater ? sub (A, B) : sub (B, A); if (!a_greater) printf ("-" ); while (C.size () != 0 ){ cout << C.back (); C.pop_back (); } 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 #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 (); i++){ t += A[i] * b; C.push_back (t % 10 ); t /= 10 ; } if (t) C.push_back (t); 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 ; }
除法
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 #include <iostream> #include <vector> #include <algorithm> using namespace std;vector<int > div (vector<int > A, int b) { vector<int > C; int t = 0 ; for (int i = A.size () - 1 ; i >= 0 ; i--){ t = t * 10 + A[i]; if (t >= b) C.push_back (t / b), t = t % b; else C.push_back (0 ); } reverse (C.begin (), C.end ()); while (C.size () > 1 && C.back () == 0 ) C.pop_back (); reverse (C.begin (), C.end ()); C.push_back (t); return C; } int main () { string a; int b; vector<int > A; cin >> a >> b; for (int i = a.size () - 1 ; i >= 0 ; i--) A.push_back (a[i] - '0' ); auto C = div (A, b); for (int i = 0 ; i < C.size () - 1 ; i++) printf ("%d" , C[i]); printf ("\n%d" , C.back ()); return 0 ; }
前缀与差分
前缀和(一维)
对于一个普通的一维数组(从 a1 = a[1] 开始,a[0] = 0),前n项和称为前缀和。通过Sn - S(k-1)可以得到S[k,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 #include <iostream> using namespace std;const int N = 100010 ;int a[N], s[N];int main () { int n, m; 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 ; }
子矩阵的和
796. 子矩阵的和 - AcWing题库
要求解一个矩阵的子矩阵的元素和,只需要想象一个矩阵,切割成十字形,按需拼凑即可。
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 #include <iostream> using namespace std;const int N = 1010 ;int a[N][N], s[N][N];int main () { int n, m, q; 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]); for (int i = 1 ; i <= n; i++) for (int j = 1 ; j <= m; 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 ; }
差分
一维差分
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 #include <iostream> using namespace std;const int N = 100010 ;int a[N], b[N];void insert (int l, int r, int c) { b[l] += c; b[r + 1 ] -= c; } int main () { int n, m; scanf ("%d%d" , &n, &m); for (int i = 1 ; i <= n; i++) scanf ("%d" , &a[i]); for (int i = 1 ; i <= n; 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 ]; for (int i = 1 ; i <= n; i++) printf ("%d " , b[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 #include <iostream> using namespace std;const int N = 1010 ;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 () { int n, m, q; 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]); for (int i = 1 ; i <= n; i++) for (int j = 1 ; j <= m; 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++) a[i][j] = a[i][j - 1 ] + a[i - 1 ][j] - a[i - 1 ][j - 1 ] + b[i][j]; for (int i = 1 ; i <= n; i++){ for (int j = 1 ; j <= m; j++) printf ("%d " , a[i][j]); printf ("\n" ); } return 0 ; }