voidquick_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); }
// 二分求出x对应的离散化的值 intfind(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 }
int son[N][26], cnt[N], idx; // 0号点既是根节点,又是空节点 // son[][]存储树中每个节点的子节点 // cnt[]存储以每个节点结尾的单词数量
// 插入一个字符串 voidinsert(char *str) { int p = 0; for (int i = 0; str[i]; i ++ ) { int u = str[i] - 'a'; if (!son[p][u]) son[p][u] = ++ idx; p = son[p][u]; } cnt[p] ++ ; }
// 查询字符串出现的次数 intquery(char *str) { int p = 0; for (int i = 0; str[i]; i ++ ) { int u = str[i] - 'a'; if (!son[p][u]) return0; p = son[p][u]; } return cnt[p]; }
并查集
p[i] 存放的是节点 i 的父节点
判断两个节点是否属于一个集合,直接判断两个节点的祖宗节点是否相等即可。
find(x) 函数返回的就是x的祖宗节点,其中还进行了优化,将每一个节点都直接指向了祖宗节点。
合并a、b两个节点,就是将a的祖宗节点指向b的祖宗节点即可。相当于给 a 的祖宗节点找了个爸😄。p[find(a)] = find(b)
// 交换两个点,及其映射关系 voidheap_swap(int a, int b) { swap(ph[hp[a]],ph[hp[b]]); swap(hp[a], hp[b]); swap(h[a], h[b]); }
voiddown(int u) { int t = u; if (u * 2 <= size && h[u * 2] < h[t]) t = u * 2; if (u * 2 + 1 <= size && h[u * 2 + 1] < h[t]) t = u * 2 + 1; if (u != t) { heap_swap(u, t); down(t); } }
voidup(int u)// 在堆尾插入新节点并依次与其父节点进行比较交换 { while (u / 2 && h[u] < h[u / 2]) { heap_swap(u, u / 2); u >>= 1; } }
for (int i = 0; i < n - 1; i ++ ) { int t = -1; // 在还未确定最短路的点中,寻找距离最小的点 for (int j = 1; j <= n; j ++ ) if (!st[j] && (t == -1 || dist[t] > dist[j])) t = j;
// 如果第n次迭代仍然会松弛三角不等式,就说明存在一条长度是n+1的最短路径,由抽屉原理,路径中至少存在两个相同的点,说明图中存在负权回路。 for (int i = 0; i < n; i ++ ) { for (int j = 0; j < m; j ++ ) { int a = edges[j].a, b = edges[j].b, w = edges[j].w; if (dist[b] > dist[a] + w) dist[b] = dist[a] + w; } }
if (dist[n] > 0x3f3f3f3f / 2) return-1; return dist[n]; }
spfa算法
本质就是使用 bfs 对 Bellman-Ford 算法进行优化
伪代码
1 2 3 4 5 6 7
queue <- 1; while queue不空 t <- q.front() q.pop() 更新t的所有出边 t -> b if b not in queue q.push(b)
queue<int> q; for (int i = 1; i <= n; i ++ ) { q.push(i); st[i] = true; }
while (q.size()) { auto t = q.front(); q.pop();
st[t] = false;
for (int i = h[t]; i != -1; i = ne[i]) { int j = e[i]; if (dist[j] > dist[t] + w[i]) { dist[j] = dist[t] + w[i]; cnt[j] = cnt[t] + 1; if (cnt[j] >= n) returntrue; // 如果从1号点到x的最短路中包含至少n个点(不包括自己),则说明存在环 if (!st[j]) { q.push(j); st[j] = true; } } } }
returnfalse; }
Floyd算法
本质是动态规划,三维,优化了一维
伪代码
1 2 3 4
for(int k = 1; k <= n; k ++) for(int i = 1; i <= n; i ++) for(int j = 1; j <= n; j ++) dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j]);
时间复杂度: O(n ^ 3), n 表示点数
1 2 3 4 5 6 7 8 9 10 11 12 13 14
初始化: for (int i = 1; i <= n; i ++ ) for (int j = 1; j <= n; j ++ ) if (i == j) d[i][j] = 0; else d[i][j] = INF;
// 算法结束后,d[a][b]表示a到b的最短距离 voidfloyd() { for (int k = 1; k <= n; k ++ ) for (int i = 1; i <= n; i ++ ) for (int j = 1; j <= n; j ++ ) d[i][j] = min(d[i][j], d[i][k] + d[k][j]); }
int n; // n表示点数 int h[N], e[M], ne[M], idx; // 邻接表存储图 int color[N]; // 表示每个点的颜色,-1表示未染色,0表示白色,1表示黑色
// 参数:u表示当前节点,c表示当前点的颜色 booldfs(int u, int c) { color[u] = c; for (int i = h[u]; i != -1; i = ne[i]) { int j = e[i]; if (color[j] == -1) { if (!dfs(j, !c)) returnfalse; } elseif (color[j] == c) returnfalse; // 和i连通的j也是c颜色,则矛盾,return false }
returntrue; }
boolcheck() { memset(color, -1, sizeof color); bool flag = true; for (int i = 1; i <= n; i ++ ) if (color[i] == -1) if (!dfs(i, 0)) { flag = false; break; } return flag; }
int n1, n2; // n1表示第一个集合中的点数,n2表示第二个集合中的点数 int h[N], e[M], ne[M], idx; // 邻接表存储所有边,匈牙利算法中只会用到从第一个集合指向第二个集合的边,所以这里只用存一个方向的边 int match[N]; // 存储第二个集合中的每个点当前匹配的第一个集合中的点是哪个 bool st[N]; // 表示第二个集合中的每个点是否已经被遍历过
boolfind(int x) { for (int i = h[x]; i != -1; i = ne[i]) { int j = e[i]; if (!st[j]) { st[j] = true; if (match[j] == 0 || find(match[j])) { match[j] = x; returntrue; } } }
returnfalse; }
// 求最大匹配数,依次枚举第一个集合中的每个点能否匹配第二个集合中的点 int res = 0; for (int i = 1; i <= n1; i ++ ) { // 每次都需要初始化,是为了确保集合1中后面节点能够继续尝试已经匹配的集合2中的节点 memset(st, false, sizeof st); if (find(i)) res ++ ; }
数学知识
质数的判定
定义:对所有大于1的自然数,若除了1和本身不具备其他约数,则该数为质数或者叫素数。
试除法判定质数
若x小于2,则不是质数
然后 i 从 2 开始遍历到 x / i 即可。时间复杂度为:O($\sqrt{n}$)
1 2 3 4 5 6 7 8
boolis_prime(int x) { if (x < 2) returnfalse; for (int i = 2; i <= x / i; i ++ ) // 因为i不能整除,则x/i肯定也不能整除。 if (x % i == 0) returnfalse; returntrue; }
试除法分解质因数
时间复杂度:O($\log{n}$) ~ O($\sqrt{n}$)
1 2 3 4 5 6 7 8 9 10 11 12
voiddivide(int x) { for (int i = 2; i <= x / i; i ++ ) if (x % i == 0) // i是x的一个因数并且一定是质数 { int s = 0; while (x % i == 0) x /= i, s ++ ; // 求i的次数 cout << i << ' ' << s << endl; } if (x > 1) cout << x << ' ' << 1 << endl; cout << endl; }
素数
朴素筛法求素数
核心原理:当遍历到某一个数 i ,就将其所有倍数全部删掉,然后若当前这个数没有被删掉,则说明从2 ~ i - 1,没有其约数,则其为质数。
优化 :每次删除时只需要将质数的倍数删除即可。
时间复杂度:O(n $\log(\log{n})$)
1 2 3 4 5 6 7 8 9 10 11 12 13
int primes[N], cnt; // primes[]存储所有素数 bool st[N]; // st[x]存储x是否被筛掉
voidget_primes(int n) { for (int i = 2; i <= n; i ++ ) { if (st[i]) continue; primes[cnt ++ ] = i; for (int j = i + i; j <= n; j += i) st[j] = true; } }
线性筛法求素数
核心原理:n 只会被其最小质因子筛掉。
时间复杂度:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
int primes[N], cnt; // primes[]存储所有素数 bool st[N]; // st[x]存储x是否被筛掉
voidget_primes(int n) { for (int i = 2; i <= n; i ++ ) { if (!st[i]) primes[cnt ++ ] = i; for (int j = 0; primes[j] <= n / i; j ++ ) // 从小到大枚举所有质数 { st[primes[j] * i] = true; if (i % primes[j] == 0) break; // primes[j] 一定是i的最小质因子 } } }
约数
试除法求所有约数
算法思想:就是暴力,每次可以存两个数 i 和 n/i,前提是两者不相等。
时间复杂度:O($\sqrt{n}$)
1 2 3 4 5 6 7 8 9 10 11 12
vector<int> get_divisors(int x) { vector<int> res; for (int i = 1; i <= x / i; i ++ ) if (x % i == 0) { res.push_back(i); if (i != x / i) res.push_back(x / i); } sort(res.begin(), res.end()); return res; }
intgcd(int a, int b) { return b ? gcd(b, a % b) : a; }
欧拉函数
欧拉函数定义
朴素版求欧拉函数,将N分解质因子,并求欧拉函数。
时间复杂度:O($\sqrt{n}$)
1 2 3 4 5 6 7 8 9 10 11 12 13
intphi(int x) { int res = x; for (int i = 2; i <= x / i; i ++ ) if (x % i == 0) { res = res / i * (i - 1); while (x % i == 0) x /= i; } if (x > 1) res = res / x * (x - 1);
return res; }
筛选法求欧拉函数
时间复杂度:O(${n}$)
代码解释:
质数 i 的欧拉函数即为phi[i] = i - 1,1 ~ i - 1均与 i 互质,共i-1个。
phi[primes[j] * i] 分为两种情况:
i % primes[j] == 0 时:primes[j] 是 i 的最小质因子,也是primes[j] * i 的最小质因子,因此1 - 1 / primes[j] 这一项在 phi[i]中计算过了,只需要将基数N修正为primes[j]倍即可,最终结果为phi[i] * primes[j]
i % primes[j] != 0 时:primes[j] 不是 i 的质因子,只是primes[j] * i 的最小质因子,因此不仅需要将基数N修正为primes[j]倍,还需要跑补上1 - 1 / primes[j]这一项。则最终结果为phi[i] * (primes[j] - 1)
intqmi(int m, int k, int p) { int res = 1 % p, t = m; while (k) { if (k&1) res = res * t % p; // 当前位为1 t = t * t % p; k >>= 1; } return res; }
扩展欧几里得算法
裴蜀定理
对于任意正整数a,b,那么一定存在正数x,y使得 $ax + by = gcd(a,b)$ 。gcd(a,b) 是a和b的最大公约数,也是 a 和 b 能凑成的最小正整数。
求解过程
当 b == 0 时,$ax + by = a$,因此有 $x = 1, y = 0$
当 b != 0 时,
1 2 3 4 5 6 7 8 9 10 11 12
// 求x, y,使得ax + by = gcd(a, b) intexgcd(int a, int b, int &x, int &y) { if (!b) // b == 0, 此时a为最大公约数 则 ax + b0 = a { x = 1; y = 0; return a; } int d = exgcd(b, a % b, y, x); // by + (a mod b)x = d y -= (a/b) * x; return d; }
// a[N][N]是增广矩阵 intgauss() { int c, r; for (c = 0, r = 0; c < n; c ++ ) { int t = r; for (int i = r; i < n; i ++ ) // 找到绝对值最大的行 if (fabs(a[i][c]) > fabs(a[t][c])) t = i;
if (fabs(a[t][c]) < eps) continue;
for (int i = c; i <= n; i ++ ) swap(a[t][i], a[r][i]); // 将绝对值最大的行换到最顶端 for (int i = n; i >= c; i -- ) a[r][i] /= a[r][c]; // 将当前行的首位变成1 for (int i = r + 1; i < n; i ++ ) // 用当前行将下面所有的列消成0 if (fabs(a[i][c]) > eps) for (int j = n; j >= c; j -- ) a[i][j] -= a[r][j] * a[i][c];
r ++ ; }
if (r < n) { for (int i = r; i < n; i ++ ) if (fabs(a[i][n]) > eps) return2; // 无解 return1; // 有无穷多组解 }
for (int i = n - 1; i >= 0; i -- ) for (int j = i + 1; j < n; j ++ ) a[i][n] -= a[i][j] * a[j][n];