基础算法 排序 1. 快速排序 是基于分治的一种排序 思想 :
确定分界点 x,x 可以 ql||q(l+r)/2||qr,最好取中间值,因为取俩边的值会有边界问题存在
调整区间,使得 q 被分成两个部分,位于 x 左边的值全部小于 x,位于 x 右边的值全部大于 x
递归处理左右两端,注意处理 l>=r 的边界情况。
调整区间 :
定义两个指针 i,j。i 指向 l-1 的位置,j 指向 r+1 的位置
移动指针 i,直到遇到比 x 大的数
移动指针 j,直到遇到比 x 小的数
如果 i 和 j 没有相遇,交换 ai 和 aj,如果相遇,那么返回
递归处理左右区间
代码 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 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); }
2. 归并排序 思路 :
找分界点,mid=(l+r)/2
递归左右两边
定义两个指针 i,j,i 指向左序列,j 指向右序列
归并,合二为一
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 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]; }
使用递归排序可以求出逆序对的数量
二分 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 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; }
浮点数二分 1 2 3 4 5 6 7 8 9 10 11 12 13 14 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; }
高精度 高精度加法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 vector<int > add (vector<int > &a,vector<int > &b) { vector<int > c; int t=0 ; for (int i=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/=10 ; } if (t)c.push_back (1 ); 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 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; }
高精度比大小 1 2 3 4 5 6 7 8 9 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 ; }
高精度乘低精度 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 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 16 vector<int > mul (vector<int > &A, vector<int > &B) { vector<int > C (A.size() + B.size()) ; for (int i = 0 ; i < A.size (); i++) for (int j = 0 ; j < B.size (); j++) C[i + j] += A[i] * B[j]; for (int i = 0 , t = 0 ; i < C.size (); i++) { t += C[i]; C[i] = 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 16 17 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; }
高精度除高精度 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 vector<int > div (vector<int > &A, vector<int > &B, vector<int > &r) { vector<int > C; if (!cmp (A, B)) { C.push_back (0 ); r.assign (A.begin (), A.end ()); return C; } int j = B.size (); r.assign (A.end () - j, A.end ()); while (j <= A.size ()) { int k = 0 ; while (cmp (r, B)) { r = sub (r, B); k ++; } C.push_back (k); if (j < A.size ()) r.insert (r.begin (), A[A.size () - j - 1 ]); if (r.size () > 1 && r.back () == 0 ) r.pop_back (); j++; } reverse (C.begin (), C.end ()); while (C.size () > 1 && C.back () == 0 ) C.pop_back (); return C; }
前缀和 前缀和可以用于快速计算一个序列的区间和,,思想很重要
一维前缀和 1 2 预处理:s[i]=a[i]+a[i-1 ]; 求区间[l,r]的和:sum=s[r]-s[l-1 ]
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 const int N=100010 ;int a[N];int main () { int n,m; scanf ("%d" ,&n); for (int i=1 ;i<=n;i++)scanf ("%d" ,&a[i]); for (int i=1 ;i<=n;i++)a[i]=a[i-1 ]+a[i]; scanf ("%d" ,&m); while (m--){ int l,r; scanf ("%d%d" ,&l,&r); printf ("%d\n" ,a[r]-a[l-1 ]); } return 0 ; }
二维前缀和 1 2 计算从左上角元素开始的一个矩阵的和:s[x][y]=s[x-1 ][y]+s[x][y-1 ]-s[x-1 ][y-1 ]+a[x][y]; 计算子矩阵的和:s=s[x2][y2]-s[x1-1 ][y2]-s[x2][y1-1 ]+s[x1-1 ][y1-1 ];
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 int s[1010 ][1010 ];int n,m,q;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" ,&s[i][j]); for (int i=1 ;i<=n;i++) for (int j=1 ;j<=m;j++) s[i][j]+=s[i-1 ][j]+s[i][j-1 ]-s[i-1 ][j-1 ]; 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 ; }
差分 是前缀和的逆运算,对于一个数组啊,其差分数组 b 的每一项都是 a[i]的钱一项 a[i-1]的差
原 a[i]和差分数组 b[i]的关系:
b[i]=a[i]-a[i-1]
a[i]=b[i]+b[i-1]
为什么要使用差分数组呢?当我们想给一个区间中的每一个数都加上一个数时,我们一般需要给每一个单独相加才可以实现。 若果我们使用差分数组,那么我们只需要获得区间[l,r],然后在差分数组 b[l]+c,b[r+1]-c,就可以得到相同的效果。时间复杂度从 O(n)缩短为 O(1)
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 using namespace std;int a[100010 ],s[100010 ];int main () { int n,m; cin>>n>>m; for (int i=1 ;i<=n;i++)cin>>a[i]; for (int i=1 ;i<=n;i++)s[i]=a[i]-a[i-1 ]; while (m--){ int l,r,c; cin>>l>>r>>c; s[l]+=c; s[r+1 ]-=c; } for (int i=1 ;i<=n;i++){ s[i]+=s[i-1 ]; cout<<s[i]<<' ' ; } return 0 ; }
二维差分 与原数组 a[i][j]的关系:
1 2 b[x1,y1]+=c;b[x2+1 ][y1]-=c; b[x1,y2+1 ]-=c;b[x2+1 ,y2+1 ]+=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 43 44 45 46 47 const int N = 1e3 + 10 ;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; cin >> n >> m >> q; for (int i = 1 ; i <= n; i++) for (int j = 1 ; j <= m; j++) cin >> 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; cin >> 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 ]; } } for (int i = 1 ; i <= n; i++) { for (int j = 1 ; j <= m; j++) { printf ("%d " , b[i][j]); } printf ("\n" ); } return 0 ; }
位运算 1 2 求n的第k位数字:n>>k&1 ; 返回x的最后一位1 :lowbit (x)=x&-x;
双指针算法 双指针不是一个算法,而是一个思想,当我们可以用 O(n2)来解决时,就可以尝试将其通过双指针或者其它方式来将其简化为一维
1 2 3 4 5 6 7 8 for (int i = 0 , j = 0 ; i < n; i ++ ){ while (j < i && check (i, j)) j ++ ; } 常见问题分类: (1 ) 对于一个序列,用两个指针维护一段区间 (2 ) 对于两个序列,维护某种次序,比如归并排序中合并两个有序序列的操作
离散化 离散化的本质是建立了一段数列到自然数之间的映射关系(value -> index),通过建立新索引,来缩小目标区间,使得可以进行一系列连续数组可以进行的操作比如二分,前缀和等…当我们数的值得区间超级大,而我们数的个数不多时,我们就可以考虑使用离散化来映射每一个数*
离散化首先需要排序去重:
1 2 3 4 5 6 离散化的本质是建立了一段数列到自然数之间的映射关系(value -> index),通过建立新索引,来缩小目标区间,使得可以进行一系列连续数组可以进行的操作比如二分,前缀和等… 离散化首先需要排序去重: 排序:sort (alls.begin (),alls.end ()) 去重:alls.earse (unique (alls.begin (),alls.end ()),alls.end ());
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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 ; }
示例:
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 typedef pair<int , int > PII;const int N = 300010 ;int n, m;int a[N], s[N];vector<int > alls; vector<PII> add, query; 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 ; } int main () { cin >> n >> m; for (int i = 0 ; i < n; i ++ ) { int x, c; cin >> x >> c; add.push_back ({x, c}); alls.push_back (x); } for (int i = 0 ; i < m; i ++ ) { int l, r; cin >> l >> r; query.push_back ({l, r}); alls.push_back (l); alls.push_back (r); } sort (alls.begin (), alls.end ()); alls.erase (unique (alls.begin (), alls.end ()), alls.end ()); for (auto item : add) { int x = find (item.first); a[x] += item.second; } for (int i = 1 ; i <= alls.size (); i ++ ) s[i] = s[i - 1 ] + a[i]; for (auto item : query) { int l = find (item.first), r = find (item.second); cout << s[r] - s[l - 1 ] << endl; } return 0 ; }
区间合并 在一个由若干个区间组成的一个集合中,将存在相同子区间的区间合并为一个区间
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 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; }