排序算法
快速排序(基础版-基准值取左端点)
#include "bits/stdc++.h"
using namespace std;
void quickSort(vector<int> &nums, int left, int right) {
if (left >= right) return; // 递归终止条件
int l = left, r = right;
int pivot = nums[l]; // 选取基准值
while (l < r) {
while (l < r && nums[r] >= pivot) r--; // 从右边找到第一个小于基准值的数
nums[l] = nums[r];
while (l < r && nums[l] <= pivot) l++; // 从左边找到第一个大于等于基准值的数
nums[r] = nums[l];
} // 退出循环时 l == r
nums[l] = pivot; // 将基准值放到中间
quickSort(nums, left, l - 1); // 递归处理左边
quickSort(nums, l + 1, right); // 递归处理右边
}
int main() {
int n;
cin >> n;
vector<int> nums(n);
for (auto &i: nums) cin >> i;
quickSort(nums, 0, n - 1);
for (auto i: nums) cout << i << " ";
cout << endl;
return 0;
}
快速排序(基础版-基准值取中点或者随机值)
#include "bits/stdc++.h"
using namespace std;
void quickSort(vector<int> &nums, int left, int right) {
if (left >= right) return; // 递归终止条件
int l = left, r = right;
int c = rand() % (r - l + 1) + l; // 随机选取基准值
// int c = (l + r) / 2; // 选取中间值
int pivot = nums[c]; // 选取基准值
swap(nums[c], nums[l]); // 将基准值放到最左边
while (l < r) {
while (l < r && nums[r] >= pivot) r--; // 从右边找到第一个小于基准值的数
nums[l] = nums[r];
while (l < r && nums[l] <= pivot) l++; // 从左边找到第一个大于等于基准值的数
nums[r] = nums[l];
} // 退出循环时 l == r
nums[l] = pivot; // 将基准值放到中间
quickSort(nums, left, l - 1); // 递归处理左边
quickSort(nums, l + 1, right); // 递归处理右边
}
int main() {
int n;
cin >> n;
vector<int> nums(n);
for (auto &i: nums) cin >> i;
quickSort(nums, 0, n - 1);
for (auto i: nums) cout << i << " ";
cout << endl;
return 0;
}
归并排序
#include "bits/stdc++.h"
using namespace std;
// 归并排序函数,对v数组中下标从left到right的元素进行排序,辅助数组为tmp
void mergeSort(vector<int> &v, int left, int right, vector<int> &tmp) {
// 如果left>=right,则序列中只有一个元素或者没有元素,直接返回
if (left >= right) return;
// 将序列从中间分为两个子序列,递归对两个子序列进行排序
int mid = (left + right) / 2;
mergeSort(v, left, mid, tmp);
mergeSort(v, mid + 1, right, tmp);
// 合并两个有序子序列
int i = left, j = mid + 1;
int k = 0;
while (i <= mid && j <= right) {
if (v[i] <= v[j])tmp[k++] = v[i++];
else tmp[k++] = v[j++];
}
while (i <= mid) tmp[k++] = v[i++];
while (j <= right) tmp[k++] = v[j++];
// 将tmp中的有序序列复制回原数组v中
// 注意这里的下标
for (int c = left, k = 0; c <= right; c++, k++) v[c] = tmp[k];
}
int main() {
int n;
cin >> n;
vector<int> nums(n);
for (auto &i: nums) cin >> i;
vector<int> tmp(n);
mergeSort(nums, 0, n - 1, tmp);
for (auto i: nums) cout << i << " ";
cout << endl;
return 0;
}
二分算法
整数二分模板
bool check(int x) {/* ... */} // 检查x是否满足某种性质
// 区间[l, r]被划分成[l, mid]和[mid + 1, r]时使用:
int bsearch_1(int l, int r)
{
while (l < r)
{
int mid = l + r >> 1;
if (check(mid)) r = mid; // check()判断mid是否满足性质
else l = mid + 1;
}
return l;
}
// 区间[l, r]被划分成[l, mid - 1]和[mid, r]时使用:
int bsearch_2(int l, int r)
{
while (l < r)
{
int mid = l + r + 1 >> 1; // 此时mid如果不+1,会死循环
if (check(mid)) l = mid;
else r = mid - 1;
}
return l;
}
数的范围
给定一个按照升序排列的长度为 n 的整数数组,以及 q 个查询。
对于每个查询,返回一个元素 k 的起始位置和终止位置(位置从 0 开始计数)。
如果数组中不存在该元素,则返回 -1 -1
。
#include "bits/stdc++.h"
using namespace std;
int lower_bound(vector<int> &a, int x) {
int l = 0, r = a.size() - 1;
while (l < r) {
int mid = (l + r) >> 1;
if (a[mid] >= x) r = mid;
else l = mid + 1;
}
if (a[l] != x) return -1;
return l;
}
int upper_bound(vector<int> &a, int x) {
int l = 0, r = a.size() - 1;
while (l < r) {
int mid = (l + r + 1) >> 1; // 此时mid如果不+1,会死循环
if (a[mid] <= x) l = mid;
else r = mid - 1;
}
if (a[l] != x) return -1;
return l;
}
int main() {
int n, q;
cin >> n >> q;
vector<int> a(n);
for (int i = 0; i < n; i++) {
cin >> a[i];
}
int t;
while (q--) {
cin >> t;
int l = lower_bound(a, t);
if (l == -1) cout << "-1 -1" << endl;
else cout << l << " " << upper_bound(a, t) << endl;
}
return 0;
}
浮点数二分(开平方)
#include "bits/stdc++.h"
using namespace std;
double mysqrt(double x) { // 思路在[0,x]内进行二分逼近直至小于某一个精度
double l = 0, r = x;
// for (int i = 0; i < 100; i++){ // 或者采用这种方法直接循环100次
while (r - l >= 1e-8) {
double mid = (l + r) / 2;
if (mid * mid >= x) r = mid;
else l = mid;
}
return l;
}
int main() {
double a;
cin >> a;
cout << mysqrt(a) << endl;
return 0;
}
大整数计算
A+B (大整数相加)
#include "bits/stdc++.h"
using namespace std;
// A和B分别为两个大数的每一位数字
vector<int> add(vector<int>& A, vector<int>& B) {
int l1 = A.size(), l2 = B.size();
int l = max(l1, l2);
int carry = 0; // 进位
vector<int> C; // 存放结果
for (int i = 0; i < l; i++) {
if (i < l1) carry += A[i]; // A的第i位数字
if (i < l2) carry += B[i]; // B的第i位数字
C.push_back(carry % 10); // 将结果加入C中
carry /= 10; // 更新进位
}
if (carry) C.push_back(carry); // 如果还有进位,将其加入C中
return C; // 返回结果
}
int main() {
string a, b;
cin >> a >> b; // a = "123456"
vector<int> A(a.size(), 0);
vector<int> B(b.size(), 0);
// 将字符串逆序存入整数数组中
int idx = 0;
for (int i = a.size() - 1; i >= 0; i--) A[idx++] = a[i] - '0'; // A=>[6, 5, 4, 3, 2 ,1]
idx = 0;
for (int i = b.size() - 1; i >= 0; i--) B[idx++] = b[i] - '0';
auto C = add(A, B);
for (int i = C.size() - 1; i >= 0; i--) cout << C[i];
cout << endl;
return 0;
}
A-B (大整数相减)
#include "bits/stdc++.h"
using namespace std;
bool cmp(vector<int> &A, vector<int> &B) { // 判断A是否大于等于B
if (A.size() != B.size()) return A.size() > B.size();
int n = A.size();
for (int i = n - 1; i >= 0; --i) {
if (A[i] != B[i]) return A[i] > B[i];
}
return true;
}
// A和B分别为两个大数的每一位数字,且保证A一定大于等于B
vector<int> subtraction(vector<int> &A, vector<int> &B) {
int n = A.size();
vector<int> C;
int t = 0; // 借位
for (int i = 0; i < n; ++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; // 无借位
}
// 去除前导0, 排除只有一个0的情况
while (C.size() > 1 && C.back() == 0) C.pop_back();
return C;
}
int main() {
string a, b;
cin >> a >> b; // a = "123456"
vector<int> A(a.size(), 0);
vector<int> B(b.size(), 0);
// 将字符串逆序存入整数数组中
int idx = 0;
for (int i = a.size() - 1; i >= 0; i--) A[idx++] = a[i] - '0'; // A=>[6, 5, 4, 3, 2 ,1]
idx = 0;
for (int i = b.size() - 1; i >= 0; i--) B[idx++] = b[i] - '0';
if (cmp(A, B)) { // A >= B
auto C = subtraction(A, B);
for (int i = C.size() - 1; i >= 0; i--) cout << C[i];
cout << endl;
} else { // A < B
auto C = subtraction(B, A);
cout << "-";
for (int i = C.size() - 1; i >= 0; i--) cout << C[i];
cout << endl;
}
return 0;
}
A*b (大整数与小整数相乘)
#include "bits/stdc++.h"
using namespace std;
vector<int> multiply(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 + t; // 直接相乘即可
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; // a = "123456"
vector<int> A(a.size(), 0);
// 将字符串逆序存入整数数组中
int idx = 0;
for (int i = a.size() - 1; i >= 0; i--) A[idx++] = a[i] - '0'; // A=>[6, 5, 4, 3, 2 ,1]
auto C = multiply(A, b);
for (int i = C.size() - 1; i >= 0; i--) cout << C[i];
cout << endl;
return 0;
}
A/b (大整数除以小整数)
#include "bits/stdc++.h"
using namespace std;
// 将 A 数组中的数按 b 进制进行除法运算,返回商 C 数组和余数 r
vector<int> div(vector<int> &A, int b, int &r) {
vector<int> C; // 存放商的数组
r = 0; // 余数初始化为 0
for (int i = A.size() - 1; i >= 0; i--) { // 从 A 数组的最高位开始逐位进行除法运算
r = r * 10 + A[i]; // 将余数乘以 10 再加上当前位的数
C.push_back(r / b); // 将商加入 C 数组
r %= b; // 更新余数
}
reverse(C.begin(), C.end()); // 将 C 数组翻转, 使得低位存储在下标小的地方
// 去掉 C 数组末尾的 0
while (C.size() > 1 && C.back() == 0) C.pop_back();
return C; // 返回商 C 数组
}
int main() {
string a;
int b;
cin >> a >> b; // a = "123456"
vector<int> A(a.size(), 0);
// 将字符串逆序存入整数数组中
int idx = 0;
for (int i = a.size() - 1; i >= 0; i--) A[idx++] = a[i] - '0'; // A=>[6, 5, 4, 3, 2 ,1]
int r = 0;
auto C = div(A, b, r);
for (int i = C.size() - 1; i >= 0; i--) cout << C[i];
cout << endl;
cout << r << endl;
return 0;
}
前缀和
1维前缀和 (计算[l, r]之间的区间和)
输入一个长度为 n 的整数序列。接下来再输入 m个询问,每个询问输入一对l,r
。对于每个询问,输出原序列中从第 l 个数到第 r 个数的和。
#include "bits/stdc++.h"
using namespace std;
const int N = 100010;
int n, m;
int a[N], s[N];
int main() {
// ios::sync_with_stdio(false); 使得cin与scanf不同步
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]; // 计算前缀和, 最好从1开始便于处理边界
while (m--) {
int l, r;
scanf("%d%d", &l, &r); // 当数据量较大时, 使用scanf能比cin快一倍左右
printf("%d\n", s[r] - s[l - 1]);
}
return 0;
}
ios::sync_with_stdio(false);
是一种用于C++流输入/输出 (iostream
) 的操作。默认情况下,C++的std::cin
,std::cout
和std::cerr
对象与 C语言的stdin
,stdout
和stderr
是同步的。这意味着你可以在程序中混合使用C和C++的I/O函数,例如使用printf
和std::cout
进行输出,而不会出现顺序混乱的问题。- 然而,这种同步需要一些额外的性能开销。当你确定你的程序只使用C++的I/O函数(或只会使用C的I/O函数)时,你可以关闭这种同步,以提高I/O性能。
ios::sync_with_stdio(false);
这行代码就是关闭C++和C I/O流之间的同步。这样做后,C++ I/O流的性能可能会有所提升,但你不再能保证C++和C的I/O函数能按预期顺序输出。也就是说,混合使用printf
和std::cout
可能会导致输出的顺序不如预期。- 值得注意的是,这个命令只需要在程序中调用一次,通常在主函数开始时调用。并且,一旦你调用了
ios::sync_with_stdio(false);
,就不能再重新开启同步(即不能再调用ios::sync_with_stdio(true);
)。 - 总的来说,如果你确定只使用C++的
iostream
进行输入输出,并且对性能有较高的要求,那么使用ios::sync_with_stdio(false);
可能会有帮助。但如果你需要混合使用C和C++的I/O函数,或者对性能不太关心,那就没必要使用这个命令。
2维前缀和 (子矩阵的和)
输入一个 n
行 m
列的整数矩阵,再输入q
个询问,每个询问包含四个整数 x1,y1,x2,y2
,表示一个子矩阵的左上角坐标和右下角坐标。对于每个询问输出子矩阵中所有数的和。
#include "bits/stdc++.h"
using namespace std;
int main() {
ios::sync_with_stdio(false); // 关闭同步流,提高cin、cout的速度
int n, m, q;
cin >> n >> m >> q; // 输入n、m、q
// 初始化a、s数组
int a[n + 1][m + 1];
memset(a, 0, sizeof(a));
int s[n + 1][m + 1];
memset(s, 0, sizeof(s));
// 输入a数组
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
cin >> a[i][j];
}
}
// 计算s数组
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] + a[i][j];
}
}
// 处理询问
while (q--) {
int x1, y1, x2, y2;
cin >> x1 >> y1 >> x2 >> y2;
cout << s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1] << endl;
}
return 0;
}
差分
一维差分
一维差分主要用于解决在给定的一位数组中的某个区间[l,r]
多次加上同一个值c
的场景。
例如 输入一个长度为 n
的整数序列。接下来输入 m
个操作,每个操作包含三个整数 l,r,c
表示将序列中 [l,r]
·之间的每个数加上c
。请你输出进行完所有操作后的序列。
#include "bits/stdc++.h"
using namespace std;
const int N = 100010;
int n, m;
int a[N], b[N]; // a原数组, b差分数组, 全局初始化为0
void insert(int l, int r, int c) { // 在[l,r]区间插入c
b[l] += c;
b[r + 1] -= c;
}
int main() {
ios::sync_with_stdio(false); // 关闭同步流,提高输入输出效率
cin.tie(nullptr); // 解除 cin 与 cout 的绑定,进一步提高输入输出效率
cin >> n >> m;
for (int i = 1; i <= n; ++i) cin >> a[i];
// 差分数组可以通过模拟插入数据来实现
for (int i = 1; i <= n; ++i) insert(i, i, a[i]);
int l, r, c;
while (m--) {
cin >> l >> r >> c;
insert(l, r, c);
}
// 求原始数组
for (int i = 1; i <= n; ++i) a[i] = a[i - 1] + b[i];
// 打印输出
for (int i = 1; i <= n; ++i) cout << a[i] << " ";
cout << endl;
return 0;
}
二维差分
二维差分类比一维差分主要解决在某个子矩阵中同时加某一个数的问题
例如 输入一个 n
行 m
列的整数矩阵,再输入 q
个操作,每个操作包含五个整数 x1,y1,x2,y2,c
, 其中 (x1,y1)
和 (x2,y2)
表示一个子矩阵的左上角坐标和右下角坐标。每个操作都要将选中的子矩阵中的每个元素的值加上 c
。请你将进行完所有操作后的矩阵输出。
#include "bits/stdc++.h"
using namespace std;
const int N = 10010;
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() {
ios::sync_with_stdio(false); // 关闭同步流,提高输入输出效率
cin.tie(nullptr); // 解除 cin 与 cout 的绑定,进一步提高输入输出效率
cin >> n >> m >> q;
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
cin >> a[i][j];
insert(i, j, i, j, a[i][j]); // 构造差分矩阵
}
}
int x1, y1, x2, y2, c;
while (q--) {
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) {
a[i][j] = a[i][j - 1] + a[i - 1][j] - a[i - 1][j - 1] + b[i][j];
cout << a[i][j] << " ";
}
cout << endl;
}
return 0;
}
滑动窗口算法
模板
/* 滑动窗⼝算法框架 */
void slidingWindow(string s, string t) {
unordered_map<char, int> need, window;
for (char c : t) need[c]++;
int left = 0, right = 0;
int valid = 0;
while (right < s.size()) {
// c 是将移⼊窗⼝的字符
char c = s[right];
// 右移窗⼝
right++;
// 进⾏窗⼝内数据的⼀系列更新
...
/*** debug 输出的位置 ***/
printf("window: [%d, %d)\n", left, right);
/********************/
// 判断左侧窗⼝是否要收缩
while (window needs shrink) {
// d 是将移出窗⼝的字符
char d = s[left];
// 左移窗⼝
left++;
// 进⾏窗⼝内数据的⼀系列更新
...
}
}
}
双指针算法
双指针算法用于优化算法使得其时间复杂度从O(n^2)
到O(n)
模板
bool check(int i, int j) {} // 具体逻辑
for(int i = 0, j = 0; i < n; i++){
while (j < i && check(i, j)) j++;
// 每道题的具体逻辑
}
最长连续不重复子序列
给定一个长度为 n
的整数序列,请找出最长的不包含重复的数的连续区间,输出它的长度。
#include "bits/stdc++.h"
using namespace std;
const int N = 100010;
int n;
int a[N];
int main() {
ios::sync_with_stdio(false); // 关闭同步流,提高输入输出效率
cin.tie(nullptr); // 解除 cin 与 cout 的绑定,进一步提高输入输出效率
// 读入n和数组a
cin >> n;
for (int i = 1; i <= n; ++i) {
cin >> a[i];
}
// 初始化变量
int j = 1; // 双指针j
unordered_set<int> st; // 用于存储不重复的数字
uint64_t res = 0; // 最终结果
int i = 1; // 双指针i
// 双指针遍历数组
while (j <= n) {
int cur = a[j]; // 当前数字
j++;
// 如果当前数字已经存在于集合中,则从集合中删除i指向的数字,直到当前数字不再存在于集合中
while (st.count(cur) != 0) {
st.erase(a[i]);
i++;
}
// 将当前数字加入集合
st.insert(cur);
// 更新最终结果
res = max(res, st.size());
}
// 输出最终结果
cout << res << endl;
return 0;
}
位运算
求`n二进制表示中第k位是多少
cout << ((n >> k) & 1) << endl;
lowbit 运算
int lowbit(int n) {
// return n & (~n+1)
return n & -n;
}
应用场景
给定一个长度为 n
的数列,请你求出数列中每个数的二进制表示中 1
的个数。
#include "bits/stdc++.h"
using namespace std;
// 计算 x 的二进制表示中最低位的 1 所对应的值
inline int lowbit(int x) {
return x & -x;
}
int main() {
// 关闭同步流,提高输入输出效率
ios::sync_with_stdio(false);
// 解除 cin 与 cout 的绑定,进一步提高输入输出效率
cin.tie(nullptr);
int n;
cin >> n;
// 读入 n 个整数
vector<int> nums(n, 0);
for (int i = 0; i < n; ++i) {
cin >> nums[i];
}
// 对每个整数计算其二进制表示中 1 的个数
for (auto &item: nums) {
int res = 0;
while (item) {
item -= lowbit(item); // 每次减去最低位的 1
res++;
}
cout << res << " ";
}
cout << endl;
整数离散化
模板
vector<int> alls; // 存储所有待离散化的值
sort(alls.begin(), alls.end()); // 将所有值排序
alls.erase(unique(alls.begin(), alls.end()), alls.end()); // 去掉重复元素
// 二分求出x对应的离散化的值
int find(int x) // 找到第一个大于等于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, ...n
}
区间和
假定有一个无限长的数轴,数轴上每个坐标上的数都是 0。现在,我们首先进行 n
次操作,每次操作将某一位置 x 上的数加 c。
接下来,进行 m
次询问,每个询问包含两个整数 l
和 r
,你需要求出在区间[l,r]
之间的所有数的和。
#include "bits/stdc++.h"
using namespace std;
typedef pair<int, int> PII;
vector<PII> add, query; // 存储插入数据和查询数据的向量
vector<int> prevX; // 存储原始X坐标的向量
// 原来的X坐标==>离散化后的X坐标
int find(int x) { // 根据原始X坐标找到对应的离散化后的X坐标
int l = 0, r = prevX.size() - 1;
while (l < r) {
int mid = (l + r) >> 1; // 使用二分查找
if (prevX[mid] >= x)
r = mid;
else
l = mid + 1;
}
return r + 1; // 因为求前缀和,离散化后的坐标从1开始
}
int main() {
ios::sync_with_stdio(false); // 关闭同步流,提高输入输出效率
cin.tie(nullptr); // 解除 cin 与 cout 的绑定,进一步提高输入输出效率
int n, m;
cin >> n >> m;
int x, y;
// 读取插入数据
for (int i = 0; i < n; ++i) {
cin >> x >> y;
add.emplace_back(x, y);
prevX.push_back(x);
}
// 读取查询数据
for (int i = 0; i < m; ++i) {
cin >> x >> y;
query.emplace_back(x, y);
prevX.emplace_back(x);
prevX.emplace_back(y);
}
// 对原始X坐标进行排序和去重
sort(prevX.begin(), prevX.end());
prevX.erase(unique(prevX.begin(), prevX.end()), prevX.end());
vector<int> axis(prevX.size() + 1, 0); // 存储离散化后的X坐标上的插入数据
vector<int> preSum(prevX.size() + 1, 0); // 存储前缀和的向量
// 离散化处理原始插入数据
for (const auto &kv : add) {
int x = find(kv.first); // 离散化后的X坐标
axis[x] += kv.second; // 在离散化后的X坐标上增加插入数据的Y值
}
// 求前缀和
for (int i = 1; i <= prevX.size(); ++i) {
preSum[i] = preSum[i - 1] + axis[i]; // 计算前缀和
}
// 离散化处理原始查询数据并输出结果
for (int i = 0; i < query.size(); ++i) {
int l = find(query[i].first); // 离散化后的左边界
int r = find(query[i].second); // 离散化后的右边界
cout << preSum[r] - preSum[l - 1] << endl; // 输出查询区间内的前缀和
}
return 0;
}