基础算法与STL
0x00 指导思想
下面给出在不同数据范围下,代码的时间复杂度和算法该如何选择:
- n ≤ 30 n ≤ 30 n≤30,指数级别, d f s dfs dfs+剪枝,状态压缩 d p dp dp
- n ≤ 100 n ≤ 100 n≤100 => O ( n 3 ) O\left(n^{3}\right) O(n3), f l o y d floyd floyd, d p dp dp,高斯消元
- n ≤ 1000 n ≤ 1000 n≤1000 => O ( n 2 ) O\left(n^{2}\right) O(n2), O ( n 2 log n ) O\left(n^{2} \log n\right) O(n2logn), d p dp dp,二分,朴素版 D i j k s t r a Dijkstra Dijkstra、朴素版 P r i m Prim Prim、 B e l l m a n Bellman Bellman- F o r d Ford Ford
- n ≤ 1 e 5 n ≤ 1e5 n≤1e5 => O ( n ∗ n ) O(n * \sqrt{n}) O(n∗n),块状链表、分块、莫队
- n ≤ 1 e 6 n ≤ 1e6 n≤1e6 => O ( n log n ) O\left(n \log n\right) O(nlogn)各种 s o r t sort sort,线段树、树状数组、 s e t / m a p set/map set/map、 h e a p heap heap、拓扑排序、 d i j k s t r a + h e a p dijkstra+heap dijkstra+heap、 p r i m + h e a p prim+heap prim+heap、 K r u s k a l Kruskal Kruskal、 s p f a spfa spfa、求凸包、求半平面交、二分、 C D Q CDQ CDQ分治、整体二分、后缀数组、树链剖分、动态树
- n ≤ 1 e 7 n ≤ 1e7 n≤1e7 => O ( n ) O\left(n \right) O(n), 以及常数较小的 O ( log n ) O\left( \log n\right) O(logn)算法,单调队列、 h a s h hash hash、双指针扫描、并查集, k m p kmp kmp、 A C AC AC自动机,常数比较小的 O ( log n ) O\left( \log n\right) O(logn)的做法: s o r t sort sort、树状数组、 h e a p heap heap、 d i j k s t r a dijkstra dijkstra、 s p f a spfa spfa
- n ≤ 1 e 8 n ≤ 1e8 n≤1e8 => O ( n ) O\left(n \right) O(n)双指针扫描、 k m p kmp kmp、AC自动机、线性筛素数
- n ≤ 1 e 9 n ≤ 1e9 n≤1e9 => O ( n ) O( \sqrt{n}) O(n),判断质数
- n ≤ 1 e 18 n ≤ 1e18 n≤1e18 => O ( log n ) O\left( \log n\right) O(logn) ,最大公约数,快速幂,数位 D P DP DP
- n ≤ 1 e 1000 n ≤ 1e1000 n≤1e1000 O ( ( log n ) 2 ) O\left((\log n)^{2}\right) O((logn)2),高精度加减乘除
- n ≤ 1 e 100000 n ≤ 1e100000 n≤1e100000 O ( log k × loglog k ) O(\log k \times \operatorname{loglog} k) O(logk×loglogk), k k k表示位数 O ( log k × loglog k ) O(\log k \times \operatorname{loglog} k) O(logk×loglogk), k k k表示位数 ,高精度加减、 F F T / N T T FFT/NTT FFT/NTT
0x10 基础算法
0x11 归并排序求逆序对
long long m_sort(int q[], int l, int r)
{
if (l >= r) return 0;
int mid = l + r >> 1;
long long res = m_sort(q, l, mid) + m_sort(q, mid + 1, r);
int i = l, j = mid + 1, k = l;
while (i <= mid && j <= r) {
if (q[i] <= q[j]) tmp[k ++ ] = q[i ++ ];
else {
// 当q[i] > q[j]时,表明q[i] ~ q[mid]均大于q[j]
res += mid - i + 1;
tmp[k ++ ] = q[j ++ ];
}
}
while (i <= mid) tmp[k ++ ] = q[i ++ ];
while (j <= r) tmp[k ++ ] = q[j ++ ];
for (i = l, j = l; i <= r; i ++ , j ++ ) q[i] = tmp[j];
return res;
}
0x12 三分
0x13 前缀和(性质待补)
0x14 差分(性质待补)
0x15 高精度
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
void trans(string a, vector<int> &A)
{
for (int i = a.size() - 1; i >= 0; i -- ) A.push_back(a[i] - '0');
}
bool cmp(string a, string b)
{
if (a.size() != b.size()) return a.size() > b.size();
for (int i = 0; i < a.size(); i ++ )
if (a[i] != b[i])
return a[i] > b[i];
return true;
}
string sub(string a, string b);
string add(string a, string b) // 高精度加法
{
if (a[0] == '-' && b[0] == '-') return '-' + add(a.substr(1), b.substr(1));
else if (a[0] == '-') return sub(b, a.substr(1));
else if (b[0] == '-') return sub(a, b.substr(1));
if (a.size() < b.size()) return add(b, a);
vector<int> A, B;
trans(a, A), trans(b, B);
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 % 10);
t /= 10;
}
if (t) C.push_back(t);
string c = "";
for (int i = C.size() - 1; i >= 0; i -- ) c += (char)C[i] + '0';
return c;
}
string sub(string a, string b) // 高精度减法
{
if (a[0] == '-' && b[0] == '-') return sub(b.substr(1), a.substr(1));
else if (a[0] == '-') return '-' + add(a.substr(1), b);
else if (b[0] == '-') return add(a, b.substr(1));
if (!cmp(a, b)) return '-' + sub(b, a);
vector<int> A, B;
trans(a, A), trans(b, 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();
string s = "";
for (int i = C.size() - 1; i >= 0; i -- ) s += (char)C[i] + '0';
return s;
}
string mul(string a, string b) // 高精乘高精
{
if (a[0] == '-' && b[0] == '-') return mul(a.substr(1), b.substr(1));
else if (a[0] == '-') return '-' + mul(a.substr(1), b);
else if (b[0] == '-') return '-' + mul(a, b.substr(1));
vector<int> A, B;
trans(a, A), trans(b, B);
int num = a.size() + b.size() - 1;
vector<int> C(num + 1);
for (int i = 0; i < num; i ++ )
{
for (int j = 0; j <= i; j ++ )
C[i] += (j < a.size() ? A[j] : 0) * ((i - j) < b.size() ? B[i - j] : 0);
if (C[i] >= 0)
{
C[i + 1] += C[i] / 10;
C[i] %= 10;
}
}
while (C.size() > 1 && C.back() == 0) C.pop_back();
string s = "";
for (int i = C.size() - 1; i >= 0; i -- ) s += (char)C[i] + '0';
return s;
}
string mul(string a, int b) // 高精度乘法
{
if (a[0] == '-' && b < 0) return mul(a.substr(1), -b);
else if (a[0] == '-') return '-' + mul(a.substr(1), b);
else if (b < 0) return '-' + mul(a, -b);
vector<int> A;
trans(a, A);
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();
string s = "";
for (int i = C.size() - 1; i >= 0; i -- )
{
s += C[i] % 10 + '0';
C[i] /= 10;
while (C[i])
{
s += (char)(C[i] % 10) + '0';
C[i] /= 10;
}
}
return s;
}
string div(string a, int b, int &r) // 高精度除法带余数
{
vector<int> A, C;
trans(a, A);
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();
string s = "";
for (int i = C.size() - 1; i >= 0; i -- ) s += (char)C[i] + '0';
return s;
}
0x16 合并区间
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;
}
0x20 STL
0x21 C++输入输出同步流
ios::sync_with_stdio(0); cin.tie(0); cin.tie(0);
0x22 STL容器
vector, 变长数组,倍增的思想
size() 返回元素个数
empty() 返回是否为空
clear() 清空
front()/back()
push_back()/pop_back()
begin()/end()
[]
支持比较运算,按字典序
pair<int, int>
first, 第一个元素
second, 第二个元素
支持比较运算,以first为第一关键字,以second为第二关键字(字典序)
string,字符串
size()/length() 返回字符串长度
empty()
clear()
substr(起始下标,(子串长度)) 返回子串
c_str() 返回字符串所在字符数组的起始地址
queue, 队列
size()
empty()
push() 向队尾插入一个元素
front() 返回队头元素
back() 返回队尾元素
pop() 弹出队头元素
priority_queue, 优先队列,默认是大根堆
size()
empty()
push() 插入一个元素
top() 返回堆顶元素
pop() 弹出堆顶元素
定义成小根堆的方式:priority_queue<int, vector<int>, greater<int>> q;
stack, 栈
size()
empty()
push() 向栈顶插入一个元素
top() 返回栈顶元素
pop() 弹出栈顶元素
deque, 双端队列
size()
empty()
clear()
front()/back()
push_back()/pop_back()
push_front()/pop_front()
begin()/end()
[]
set, map, multiset, multimap, 基于平衡二叉树(红黑树),动态维护有序序列
size()
empty()
clear()
begin()/end()
++, -- 返回前驱和后继,时间复杂度 O(logn)
set/multiset
insert() 插入一个数
find() 查找一个数
count() 返回某一个数的个数
erase()
(1) 输入是一个数x,删除所有x O(k + logn)
(2) 输入一个迭代器,删除这个迭代器
lower_bound()/upper_bound()
lower_bound(x) 返回大于等于x的最小的数的迭代器
upper_bound(x) 返回大于x的最小的数的迭代器
map/multimap
insert() 插入的数是一个pair
erase() 输入的参数是pair或者迭代器
find()
[] 注意multimap不支持此操作。 时间复杂度是 O(logn)
lower_bound()/upper_bound()
unordered_set, unordered_map, unordered_multiset, unordered_multimap, 哈希表
和上面类似,增删改查的时间复杂度是 O(1)
不支持 lower_bound()/upper_bound(), 迭代器的++,--
bitset, 圧位
bitset<10000> s;
count() 返回有多少个1
any() 判断是否至少有一个1
none() 判断是否全为0
set() 把所有位置成1
set(k, v) 将第k位变成v
reset() 把所有位变成0
flip() 等价于~
flip(k) 把第k位取反
0x23 lowbit
int lowbit(int x) // 返回二进制中1的个数
{
int res = 0;
for (int i = x; i; i -= i & -i) res ++ ;
return res;
}
###0x24 去重
// 手动实现(万一有用呢doge)
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];
// a[0] ~ a[j - 1] 所有a中不重复的数
return a.begin() + j;
}
sort(alls.begin(), alls.end()); // 排序
alls.erase(unique(alls), alls.end()); // 去重
// alls.erase(unique(alls.begin(), alls.end()), alls.end()); // STLyyds
0x25 常用函数
1. 全排列:next_permutation
//全排列k次就是第k大的数
int a[];
do
{
for(int i = 0; i < n; i ++) cout << a[i] << ' ';
cout << '\n';
}
while(next_permutation(a, a + n));
2. 返回容器内最大最小值
min_element(first,last) 寻找范围内最小值,返回迭代器
max_element(first,last)寻找范围内最大值,返回迭代器
vector<int> v = {1, 2, 3};
int maxv = *max_element(v.begin(), v.end());
int minv = *min_element(v.begin(), v.end());
maxv -> 3, minv -> 1;
3. fill(first,last,value)将区间first到last区间全部初始为value
vector<int> v(10);
fill(v.begin(), v.end(), 3);
4. sort类函数
sort (first, last) 对容器或普通数组中 [first, last) 范围内的元素进行排序,默认进行
升序排序。
stable_sort (first, last) 和 sort() 函数功能相似,不同之处在于,对于 [first, last)
范围内值相同的元素,该函数不会改变它们的相对位置。
partial_sort (first, middle, last) 从 [first,last) 范围内,筛选出 muddle-first
个最小的元素并排序存放在 [first,middle) 区间中。
eg : vector<int> v = {3, 2, 5, 4, 1, 6, 9, 7};
partial_sort(v.begin(), v.begin() + 4, v.end());
//以默认的升序排序作为排序规则,将 vector 中最小的 4 个元素移动到开头位置并排好序
v -> {1 2 3 4 5 6 9 7}
is_sorted (first, last) 检测 [first, last) 范围内是否已经排好序,默认检测是否按
升序排序。
eg : vector<int> v = {1, 2, 3, 5};
is_sorted(v.begin(), v.end()) == true
5. 进制转换
stoi(str, 0, 2); //将字符串str从0位置开始到末尾的n进制转换为十进制
string str = "1010";
int a = stoi(str, 0, 2);
cout << a << '\n';
a -> 10
itoa(num, str, 2); //将一个10进制的数转化为n进制的值
int num = 10;
char str[100];
itoa(num, str, 2);
cout << str << '\n';
str -> 1010
C ++ :
cout << "36的8进制:" << std::oct << 36 << endl;
cout << "36的10进制" << std::dec << 36 << endl;
cout << "36的16进制:" << std::hex << 36 << endl;
cout << "36的2进制: " << bitset<8>(36) << endl;
6. reverse
string str = "hello world, hi";
reverse(str.begin(), str.end()); //str结果为 ih ,dlrow olleh
vector<int> v = {5, 4, 3, 2, 1};
reverse(v.begin(), v.end()); //容器v的值变为1, 2, 3, 4, 5
反转从l到r的区间
vector<int> v = {1, 2, 3, 4, 5};
int l = 2, r = 3;
reverse(v.begin() + l, v.begin() + r + 1);
v -> {1, 2, 4, 3, 5}
7. 反向迭代器及prev, next
判断字符串回文
string s;
if(s == string(s.rbegin(), s.rend())) cout << "Yes" << '\n';
prev和next
string x(s.rbegin(), s.rend()); //x是s的reverse
string y(s.rbegin(), prev(s.rend())); //y是x除掉最后一个字母(eg: s == zxy, x = yxz, y = yx)
string z(next(s.rbegin()), s.rend()); //z是s的reverse除掉第一个字母(eg: s == zxy, x = xz)
8. lower_bound() 和 upper_bound()
int a[5] = {1, 2, 3, 4, 5};
//从 a 数组中找到第一个大于等于 3 的元素
int p = lower_bound(a, a + 5, 3) - a;
p -> 2
//从 a 数组中找到第一个大于 3 的元素
int p = upper_bound(a, a + 5, 3) - a;
p -> 3
a[5] = {5, 4, 3, 2, 1};
//从 a 数组中找到第一个小于等于 3 的元素
int p = lower_bound(a, a + 5, 3, greater()) - a;
p -> 2
//从 a 数组中找到第一个小于 3 的元素
int p = upper_bound(a, a + 5, 5, greater()) - a;
p -> 3
9. __builtin函数
__builtin_popcount(x) 返回x的二进制中1的个数
__builtin_ffs (x) 返回x的最后一位1的是从后向前第几位,比如7368(1110011001000)返回4。
__builtin_clz (x) 返回前导的0的个数。
__builtin_ctz (x) 返回后面的0个个数,和__builtin_clz相对。
__builtin_parity (x) 返回x的奇偶校验位,也就是x的1的个数模2的结果。
此外,这些函数都有相应的usigned long long版本,只需要在函数名后面加上ll就可以了,
比如int __builtin_clzll。
10. Math函数()
sin() 返回参数的正弦
sinh() 返回某个角度的双曲正弦
tan() 返回参数的切线
tanh() 返回角度的双曲正切
acos() 返回反余弦数字
/*
double x = 0.0, result;
result = acos(x);
cout << "acos(x) = " << result << " radians" << endl;
cout << "acos(x) = " << result * 180 / 3.1415 << " degrees" << endl;
acos(x) = 1.5708 radians
acos(x) = 90.0027 degrees
*/
acosh() 返回数字的双曲余弦值
/*
double x = 13.21, result;
result = acosh(x);
cout << "acosh(x) = " << result << " radian" << endl;
cout << "acosh(x) = " << result * 180 / PI << " degree" << endl;
acosh(x) = 3.27269 radian
acosh(x) = 187.511 degree
*/
asin() 返回反正弦值
/*
double x = 0.25, result;
result = asin(x);
cout << "asin(x) = " << result << " radians" << endl;
cout << "asin(x) = " << result * 180 / 3.1415 << " degrees" << endl;
asin(x) = 0.25268 radians
asin(x) = 14.4779 degrees
*/
asinh() 返回数字的双曲正弦值
/*
double x = -6.82, result;
result = asinh(x);
cout << "asinh(x) = " << result << " radian" << endl;
cout << "asinh(x) = " << result * 180 / PI << " degree" << endl;
asinh(x) = -2.61834 radian
asinh(x) = -150.02 degree
*/
atan() 返回反正切数字
/*
double x = 57.74, result;
result = atan(x);
cout << "atan(x) = " << result << " radians" << endl;
//输出度数
cout << "atan(x) = " << result * 180 / 3.1415 << " degrees" << endl;
atan(x) = 1.55348 radians
atan(x) = 89.0104 degrees
*/
atan2() 返回坐标的反正切
/*
double x = 10.0, y = -10.0, result;
result = atan2(y, x);
cout << "atan2(y/x) = " << result << " radians" << endl;
cout << "atan2(y/x) = " << result * 180 / 3.141592 << " degrees" << endl;
atan2(y/x) = -0.785398 radians
atan2(y/x) = -45 degrees
*/
atanh() 返回数字的弧双曲正切
/*
double x = 0.32, result;
result = atanh(x);
cout << "atanh(x) = " << result << " radian" << endl;
cout << "atanh(x) = " << result * 180 / PI << " degree" << endl;
atanh(x) = 0.331647 radian
atanh(x) = 19.002 degree
*/
cbrt() 计算数字的立方根
copysign(x,y) 它以y的符号返回x的大小。
/*
double x = 34.15, y = -13.0, result;
result = copysign(x, y);
cout << "copysign(" << x << "," << y << ") = " << result << endl;
copysign(34.15,-13) = -34.15
*/
cos() 返回参数的余弦
/*
double x = 0.5, result;
result = cos(x);
cout << "cos(x) = " << result << endl;
double xDegrees = 25;
//将角度转换为弧度
x = xDegrees * 3.14159 / 180;
result = cos(x);
cout << "cos(x) = " << result << endl;
cos(x) = 0.877583
cos(x) = 0.906308
*/
cosh() 返回某个角度的双曲余弦值
cosh x = e x + e − x 2 \cosh x=\frac{\mathbf{e}^{x}+\mathbf{e}^{-x}}{2} coshx=2ex+e−x
/*
double x = 4.55, result;
result = cosh(x);
cout << "cosh(x) = " << result << endl;
double xDegrees = 90;
x = xDegrees * 3.14159 / 180;
result = cosh(x);
cout << "cosh(x) = " << result << endl;
cosh(x) = 47.3215
cosh(x) = 2.50918
*/
exp() 它计算并返回e的x次幂。
/*
double x = 2.19, result;
result = exp(x);
cout << "exp(x) = " << result << endl;
exp(x) = 8.93521
*/
exp2() 它计算x的以2为底的指数。
expm1() 它计算出幂乘以x减一的指数。
/*
double x = 2.19, result;
result = expm1(x);
cout << "e^" << x << " - 1 = " << result << endl;
e^2.19 - 1 = 7.93521
*/
abs() 返回一个整数的绝对值
fabs() 返回一个实数的绝对值
fdim(x,y) 返回x和y之间的正差。
/*
若x > y, 返回x - y, 如果x ≤ y 为 0
*/
ceil() 返回数字的上限值
floor() 返回十进制数字的下限值
frexp() 返回一个浮点数的尾数和指数。
/*
double x = 6.81, significand;
int *exp;
significand = frexp(x, exp);
cout << x << " = " << significand << " * 2^" << *exp << endl;
6.81 = 0.85125 * 2^3
*/
ilogb() 返回|x|的对数的整数部分
/*
int result;
double significand;
double x = 16.81;
result = ilogb(x);
significand = x / pow(FLT_RADIX, result);
cout << "ilogb (" << x << ") = " << result << endl;
cout << x << " = " << significand << " * " << FLT_RADIX << "^" << result << endl << endl;
ilogb (16.81) = 4
16.81 = 1.05062 * 2^4
*/
fma(x,y,z) 它计算表达式x * y + z。
fmax() 返回传递的两个参数中最大的
fmin() 返回两个给定参数中的最小值
fmod() 计算除法浮点数的余数
ldexp() 将x和2的乘积返回到幂e
/*
double x = 13.056, result;
int exp = 2;
result = ldexp(x, exp);
cout << "ldexp(x, exp) = " << result << endl;
ldexp(x, exp) = 52.224
*/
llrint() 使用当前舍入模式舍入参数
/*
//默认情况下,舍入方向为最接近的方向,即fesetround(FE_TONEAREST)
double x = 11.87;
long long int result;
result = llrint(x);
cout << "四舍五入到最近 (" << x << ") = " << result << endl;
//中间值四舍五入为更高的整数
x = 11.5;
result = llrint(x);
cout << "四舍五入到最近 (" << x << ") = " << result << endl;
// 将舍入方向设置为DOWNWARD
fesetround(FE_DOWNWARD);
x = 11.87;
result = llrint(x);
cout << "向下舍入 (" << x << ") = " << result << endl;
// 将舍入方向设置为UPWARD
fesetround(FE_UPWARD);
x = 33.32;
result = llrint(x);
cout << "向上舍入 (" << x << ") = " << result << endl;
四舍五入到最近 (11.87) = 12
四舍五入到最近 (11.5) = 12
向下舍入 (11.8699) = 11
向上舍入 (33.3201) = 34
*/
log() 返回数字的自然对数
log10() 返回数字的以10为底的对数
log1p() 返回x + 1的自然对数。
log2(x) 它计算x的以2为底的对数。
logb(x) 返回|x|的对数
modf() 将数字分解为整数和小数部分
/*
double x = 14.86, intPart, fractPart;
fractPart = modf(x, &intPart);
cout << x << " = " << intPart << " + " << fractPart << endl;
x = -31.201;
fractPart = modf(x, &intPart);
cout << x << " = " << intPart << " + " << fractPart << endl;
14.86 = 14 + 0.86
-31.201 = -31 + -0.201
*/
nan() 返回NaN值
/*
double src = nan("1");
uint64_t dest;
//将变量src复制到dest
//将<cstring>用于memcpy()
memcpy(&dest, &src, sizeof src);
cout << "nan(\"1\") = " << src << " (" << hex << dest << ")\n";
nan("1") = nan (7ff8000000000001)
*/
pow() 计算幂
sqrt() 计算数字的平方根