voidquick_sort(int q[], int l, int r){ if (l >= r) return; int x = q[l + r >> 1], i = l - 1, j = 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); }
#include<iostream> usingnamespacestd; constint N = 1e5 + 10; int n, k; int q[N];
intquick_sort(int q[], int l, int r, int k){ if (l == r) return q[l]; int x = q[l+r>>1], i = l-1, j = 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]); } int sl = j - l + 1; // 代表比x小的区间内数的个数 // 若k小于等于sl,说明要找的数在左半区间 if (k <= sl) return quick_sort(q, l, j, k); // 反之,在右半区间,这时要寻找的数要减去左半区间的长度 elsereturn quick_sort(q, j + 1, r, k - sl); } intmain(){ scanf("%d %d", &n, &k); for (int i = 0; i < n; i ++ ) scanf("%d", &q[i]); cout << quick_sort(q, 0, n-1, k) << endl; return0; }
归并排序
确定分界点为首末中点
以中点为界,递归排序中点两侧使其有序
归并,合二为一
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
voidmerge_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]; }
#include<iostream> usingnamespacestd; typedeflonglong LL; constint N = 1e6 + 10; int q[N], temp[N]; int n; LL res = 0;
voidmerge_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 i = l, j = mid + 1, k = 0; while (i <= mid && j <= r) { if (q[i] <= q[j]) temp[k++] = q[i++]; else { temp[k++] = q[j++]; res += mid - i + 1; } } while (i <= mid) temp[k++] = q[i++]; while (j <= r) temp[k++] = q[j++]; for (int i = l, j = 0; i <= r; i++, j++) q[i] = temp[j]; }
intmain(){ scanf("%d", &n); for (int i = 0; i < n; i ++ ) scanf("%d", &q[i]); merge_sort(q, 0, n-1); cout << res << endl; return0; }
二分
当题目具有二段性或者单调性(也可以没有)时,可以采用二分进行枚举查找
整数
版本一
边界点归于左半边,从而将[l, r]拆分成[l, mid]和[mid+1, r]
1 2 3 4 5 6 7 8 9 10 11
int l = 0, r = n-1; intbsearch_1(int l, int r) { while (l < r) { int mid = l + r >> 1; if (check(mid)) r = mid; else l = mid + 1; } return l; }
版本二
边界点归于右半边,从而将[l, r]拆分成[l, mid-1]和[mid, r]
注意,在取mid时要加1
1 2 3 4 5 6 7 8 9 10 11
int l = 0, r = n-1; intbsearch_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; }
vector<int> add(vector<int> &A, vector<int> &B){ vector<int> C; int t = 0; // t同时代表进位、进位加和 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; // 十位为新的进位 } // 如果最后一位进位不为0,则直接赋给结果的最高位 if (t) C.push_back(1); return C; }
intmain(){ 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); for (int i = C.size() - 1; i >= 0; i--) printf("%d", C[i]); return0; }
// 判断A>=B boolcmp(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]; } returntrue; // 两者相等 }
// A-B,在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; } // 避免出现类似001的情况 while (C.size() > 1 && C.back() == 0) C.pop_back(); //去掉前导0 return C; }
intmain(){ 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'); // 判断A>=B if (cmp(A, B)) { auto C = sub(A, B); for (int i = C.size() - 1; i >= 0; i--) printf("%d", C[i]); } else { auto C = sub(B, A); printf("-"); for (int i = C.size() - 1; i >= 0; i--) printf("%d", C[i]); } return0; }
vector<int> mul(vector<int> A, int b){ vector<int> C; int t = 0; for (int i = 0; i < A.size() || t; i++) { // t中可能仍存在进位 if (i < A.size()) t = A[i] * b; C.push_back(t % 10); t /= 10; } return C; }
intmain(){ 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 = mul(A, b); for (int i = C.size() - 1; i >= 0; i--) printf("%d", C[i]); return0; }
// A / b,余数r,商为C 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; }
intmain(){ 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; return0; }
前缀和
一维
前缀和指数列中前n个数的和(1 < n < 数列长度),利用前缀和可以求出数列任一区间内数的和
本质是高中数列的一个知识点ai = S(i) - S(i-1)
a[1],a[2],...,a[n]
s[i]=a[1]+a[2]+...+a[i]
这也体现出了算法题不过于数学思想的一种体现,不会数学的确也可以写代码,但肯定不能写好代码
我们计算机专业的同学学习专业课的同时,也不可忽视数学的学习
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
#include<iostream> usingnamespacestd; constint N = 100010; int n, m; int a[N], s[N]; intmain(){ 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]); } return0; }
#include<iostream> #include<algorithm> usingnamespacestd; constint N = 5010; int g[N][N];
intmain(){ int N, R; cin >> N >> R; int n = R, m = R; for (int i = 0; i < N; i++) { int x, y, w; cin >> x >> y >> w; x++, y++; // 前缀和习惯从下标1开始 g[x][y] += w; n = max(n, x), m = max(m, y); } for (int i = 1; i <= 5001; i++) for (int j = 1; j <= 5001; j++) g[i][j] += g[i-1][j] + g[i][j-1] - g[i-1][j-1]; int ans = 0; if (R >= 5000) { cout << g[5001][5001] << endl; return0; } for (int i = R; i <= 5001; i++) { for (int j = R; j <= 5001; j++) { ans = max(ans, g[i][j] - g[i-R][j] - g[i][j-R] + g[i-R][j-R]); } } cout << ans << endl; return0; }
#include<iostream> usingnamespacestd; constint N = 100010; int n, m; int a[N], b[N]; // 核心代码 voidinsert(int l, int r, int c){ b[l] += c; b[r + 1] -= c; } intmain(){ scanf("%d%d", &n, &m); for (int i = 1; i <= n; i ++ ) scanf("%d", &a[i]); // 在数组一个位置加上一个数,那么在它的下一个位置减去这一数 // 等同于b[i] = a[i] - a[i - 1] 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); // 在[l, r]区间内加上c insert(l, r, c); } // 计算前缀和,将数组b还原成数组a for (int i = 1; i <= n; i ++ ) b[i] += b[i-1]; for (int i = 1; i <= n; i ++ ) printf("%d ", b[i]); return0; }
#include<iostream> usingnamespacestd; constint N = 1e6 + 10; int a[N], s[N]; intmain(){ int n; scanf("%d", &n); for (int i = 0; i < n; i ++ ) scanf("%d", &a[i]); int res = 0; for (int i = 0, j = 0; i < n; i ++ ) { s[a[i]]++; while (s[a[i]] > 1) { s[a[j]]--; j++; } res = max(res, i - j + 1); } cout << res << endl; return0; }
位运算
一个数的二进制表示
进行移位
&1表示取出当前最后一位
1 2 3 4 5 6 7 8
#include<iostream> usingnamespacestd; intmain(){ int n = 10; for (int k = 3; k >= 0; k--) cout << (n >> k & 1) ; // 核心代码 return0; }
// 二分求出x对应离散化后的值 intfind(int x){ // 找出第一个大于等于x的位置 int l = 0, r = alls.size() - 1; while (l < r) { int mid = l + r >> 1; // x在哪,区间就往哪里缩 if (alls[mid] >= x) r = mid; else l = mid + 1; } return r + 1; // 映射到1, 2, ...n }
区间和
假定有一个无限长的数轴,数轴上每个坐标上的数都是 0。
现在,我们首先进行 n 次操作,每次操作将某一位置 x 上的数加 c。
接下来,进行 m 次询问,每个询问包含两个整数 l 和 r,你需要求出在区间 [l,r]之间的所有数的和。
#include<iostream> #include<vector> #include<algorithm> #define x first #define y second usingnamespacestd; typedefpair<int, int> PII; constint N = 300010; int n, m; int a[N], s[N]; vector<int> alls; //存储所有的位置 vector<PII> add, query; // 二分,将题目给的较大位置,映射成数组的下标 intfind(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; } intmain(){ 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) { a[find(item.x)] += item.y; } // 求其前缀和,以便求任意区间 for (int i = 1; i <= alls.size(); i++) s[i] = s[i-1] + a[i]; // 处理问询结果 for (auto t : query) { int l = find(t.x), r = find(t.y); cout << s[r] - s[l-1] << endl; } return0; }
unique()源码剖析
本质:双指针
作用:去重
条件:1)第一个元素 2)a[i] != a[i-1]
1 2 3 4 5 6 7 8
vector<int>::iterator unique(vector<int> &a){ int j = 0; for (int i = 0; i < a.size(); i++) if (!i || a[i] != a[i-1]) a[j++] = a[i]; return a.begin() + j; }
区间合并
如果两个区间有交集,则将区间合并为一个
区间与区间之间的关系分为三类:
彼此互不相交
后一个区间被前一个区间包含
后一个区间与前一个有相交的部分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
voidmerge(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; }