记录目前接触过的算法。
基础算法+数据结构 二分 1 2 3 4 5 6 7 8 9 10 11 12 int b_find (int l, int r, int x) { if (l == r){ if (a[l] == x) return l; return -1 ; } int mid = l + r >> 1 ; if (a[mid] >= x) r = mid; else l = mid + 1 ; return b_find (l, r, x); }
查找的是大于等于x的最小数的位置。
离散化
去重离散化C++
1 2 sort (alls.begin (), alls.end ());alls.erase (unique (alls.begin (), alls.end ()), alls.end ());
1 2 3 int to_find (int x) { return lower_bound (alls.begin (), alls.end (), x) - alls.begin () + 1 ; }
映射离散化
ST表 倍增思想。
ST 表是用于解决 可重复贡献问题 的数据结构。,预处理时间复杂度O(nlogn)
,查询O(1)
。
st[i][j]
表示第 i 个数开始 $2^j$ 个数中的最值。
1 2 3 4 5 6 7 8 9 10 void initlog () { for (int i = 2 ; i < N; i++)log_2[i] = log_2[i >> 1 ] + 1 ; } void initst () { for (int j = 1 ; j < 20 ; j++) { for (int i = 1 ; i + (1 << j) - 1 <= n; i++) { st[i][j] = max (st[i][j - 1 ], st[i + (1 << j - 1 )][j - 1 ]); } } }
1 2 3 4 int query (int l, int r) { int x = log_2[r - l + 1 ]; return max (st[l][x], st[r - (1 << x) + 1 ][x]); }
树状数组 单点修改 区间查询
1 2 3 4 5 6 7 8 9 10 11 12 13 int lowbit (int x) { return x & -x; }void add (int u, int k) { for (int i = u; i <= n; i += lowbit (i)) c[i] += k; } int query (int u) { int res = 0 ; for (int i = u; i > 0 ; i -= lowbit (i)) res += c[i]; return res; }
区间修改 单点询问
1 2 3 add (l, x), add (r + 1 , -x);query (x);
线段树(求和模板) 初始准备(注意开四倍的数组)
1 2 const int N = 1e5 + 10 ;int tr[N << 2 ], mark[N << 2 ];
上传与下传
1 2 3 4 5 6 7 8 9 10 11 12 void push_up (int u) { tr[u] = tr[u << 1 ] + tr[u << 1 | 1 ]; } void push_down (int u, int len) { if (len <= 1 ) return ; tr[u << 1 ] += mark[u] * (len - len / 2 ); mark[u << 1 ] += mark[u]; tr[u << 1 | 1 ] += mark[u] * (len / 2 ); mark[u << 1 | 1 ] += mark[u]; mark[u] = 0 ; }
建树
1 2 3 4 5 6 7 void build (int u = 1 , int cl = 1 , int cr = n) { if (cl == cr) return void (tr[u] = a[cl]); int mid = cl + cr >> 1 ; build (u << 1 , cl, mid); build (u << 1 | 1 , mid + 1 , cr); push_up (u); }
区间修改
1 2 3 4 5 6 7 8 9 10 11 void update (int l, int r, int k, int u = 1 , int cl = 1 , int cr = n) { if (l <= cl && cr <= r) { tr[u] += k * (cr - cl + 1 ); return void (mark[u] += k); } push_down (u, cr - cl + 1 ); int mid = cl + cr >> 1 ; if (mid >= l) update (l, r, k, u << 1 , cl, mid); if (mid < r) update (l, r, k, u << 1 | 1 , mid + 1 , cr); push_up (u); }
区间查询
1 2 3 4 5 6 7 8 int query (int l, int r, int u = 1 , int cl = 1 , int cr = n) { if (l <= cl && cr <= r) return tr[u]; push_down (u, cr - cl + 1 ); int mid = cl + cr >> 1 , ans = 0 ; if (mid >= l) ans += query (l, r, u << 1 , cl, mid); if (mid < r) ans += query (l, r, u << 1 | 1 , mid + 1 , cr); return ans; }
并查集 查询操作
1 2 3 int find (int x) { return p[x] == x ? x : p[x] = find (p[x]); }
合并操作
(可同时维护集合元素个数,距离祖宗距离等信息)
图论 最短路 1. Dijkstra(堆优化版) “每次更新距离起点dist最短的点作更新”。 $O(mlogm)$
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 priority_queue<PII, vector<PII>, greater<PII>>q; int dijkstra (int s) { dist.assign (N, INF); memset (vis, 0 , sizeof (vis)); dist[s] = 0 ; q.emplace (0 , s); while (!q.empty ()){ int u = q.top ().second; q.pop (); if (vis[u]) continue ; vis[u] = 1 ; for (auto &v : g[u]){ if (dist[v.second] > dist[u] + v.first){ dist[v.second] = dist[u] + v.first; q.emplace (dist[v.second], v.second); } } } if (dist[n] == INF) return -1 ; return dist[n]; }
2. Bellman-ford 可处理负权边。最适用于,有边数限制的最短路。(时间复杂度较大,少见) $O(mn)$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 int bellman_ford (int s) { memset (dist, 0x3f , sizeof (dist)); dist[s] = 0 ; for (int i = 0 ; i < k; i++) { memcpy (backup, dist, sizeof (dist)); for (int j = 0 ; j < m; j++) { int u = edges[j].u, v = edges[j].v, w = edges[j].w; dist[v] = min (dist[v], backup[u] + w); } } if (dist[n] > INF / 2 ) return -INF; return dist[n]; }
3. SPFA 可处理负权边(不含负环)。也可以处理正权边(可能会被卡)
vis记录在队列内的元素。一般$O(m)$ 最大$O(mn)$
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 int spfa (int s) { memset (dist, 0x3f , sizeof dist); dist[s] = 0 ; q.emplace (0 , s); vis[s] = 1 ; while (!q.empty ()){ auto u = q.front ().second; q.pop (); vis[u] = 0 ; for (auto &v: g[u]){ if (dist[v.second] > dist[u] + v.first){ dist[v.second] = dist[u] + v.first; if (!vis[v.second]){ q.emplace (dist[v.second], v.second); vis[v.second] = 1 ; } } } } return dist[n]; }
4. Floyd 邻接表存图,可处理负权边,时间复杂度$O(n^3)$
1 2 3 4 5 6 7 8 9 10 11 void floyd () { 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]); } } } }
最小生成树 1.Prim
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 int g[N][N]; int dist[N]; bool vis[N]; int prim () { memset (dist, 0x3f , sizeof dist); dist[1 ] = 0 ; int res = 0 ; for (int i = 0 ; i < n; i++) { int t = -1 ; for (int j = 1 ; j <= n; j++) if (!vis[j] && (t == -1 || dist[t] > dist[j])) t = j; if (dist[t] == INF) return INF; res += dist[t]; vis[t] = 1 ; for (int j = 1 ; j <= n; j++) dist[j] = min (dist[j], g[t][j]); } return res; }
堆优化版$O(mlogn)$,一般用于稀疏图(X)
(此时用$Kruskal$更方便)
2.Kruskal $O(mlogm)$,一般用于稀疏图
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 struct Edge { int a, b, w; }edges[M]; bool cmp (Edge &x, Edge &y) { return x.w < y.w; } int kruskal () { sort (edges, edges + m, cmp); for (int i = 1 ; i <= n; i ++ ) p[i] = i; int res = 0 , cnt = 0 ; for (int i = 0 ; i < m; i ++ ) { int a = edges[i].a, b = edges[i].b, w = edges[i].w; a = find (a), b = find (b); if (a != b) { p[a] = b; res += w; cnt ++ ; } } if (cnt < n - 1 ) return INF; return res; }
拓扑排序 拓扑排序:将集合内已知的偏序关系整合成全序
具体流程如下:
将所有入度为0的点加入处理队列
将处于队头的点x取出,遍历所有x能到达的点y
对每一个y,处理入度-1
y入度为0时,将y加入处理队列
重复以上过程直到队列为空
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 bool topo () { int cnt = 0 ; for (int i = 1 ; i <= n; i++) if (!ind[i]) q.emplace (i); while (!q.empty ()){ int u = q.front (); q.pop (); cnt++; res.emplace_back (u); for (auto &v : g[u]){ ind[v]--; if (!ind[v])q.emplace (v); } } return cnt == n; }
LCA Least Common Ancestors 最近公共祖先
树上倍增法:预处理O(nlogn)
每次查询O(logn)
f[i][j]
表示 i 节点向上走 $2^j$ 层
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 void dfs (int u, int fa) { for (auto & v : g[u]) if (v != fa) { dep[v] = dep[u] + 1 ; f[v][0 ] = u; for (int i = 1 ; i < M; i++) f[v][i] = f[f[v][i - 1 ]][i - 1 ]; dfs (v, u); } return ; } int lca (int x, int y) { if (dep[x] < dep[y])swap (x, y); for (int i = M - 1 ; i >= 0 ; i--) if (dep[f[x][i]] >= dep[y])x = f[x][i]; if (x == y)return x; for (int i = M - 1 ; i >= 0 ; i--) if (f[x][i] != f[y][i])x = f[x][i], y = f[y][i]; return f[x][0 ]; }
Tarjan缩点 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 vector<int > g[N]; int dfn[N], low[N], timestamp;int stk[N], top; bool in_stk[N];int id[N], scc_cnt, siz[N];void tarjan (int u) { dfn[u] = low[u] = ++timestamp; stk[++top] = u, in_stk[u] = 1 ; for (auto &v : g[u]){ if (!dfn[v]){ tarjan (v); low[u] = min (low[u], low[v]); } else if (in_stk[v]) low[u] = min (low[u], dfn[v]); } if (dfn[u] == low[u]){ ++scc_cnt; int y; do { y = stk[top--]; in_stk[y] = 0 ; id[y] = scc_cnt; siz[scc_cnt]++; }while (y != u); } }
数学 素数判定 1 2 3 4 5 6 bool is_Prime (int n) { if (n < 2 ) return 0 ; for (int i = 2 ; i <= n / i; i++) if (!(n % i)) return 0 ; return 1 ; }
埃氏筛 1 2 3 4 5 6 7 8 void Eratosthenes (int n) { for (int i = 2 ; i <= n; i++){ if (!comp[i]){ primes[cnt++] = i; for (int j = i + i; j <= n; j += i)comp[j] = 1 ; } } }
若是质数,加入质数表, 所有倍数判为合数。$O(nloglogn)$
欧拉筛(线性筛) 1 2 3 4 5 6 7 8 9 void Euler (int n) { for (int i = 2 ; i <= n; i++){ if (!comp[i]) primes[cnt++] = i; for (int j = 0 ; primes[j] <= n / i; j++){ comp[i * primes[j]] = 1 ; if (!(i % primes[j])) break ; } } }
若是质数,加入质数表
筛去i的质数倍,直到i成为某质数的倍数
(n只会被最小质因子筛掉)
求所有约数 1 2 3 4 5 6 7 8 9 10 11 vector<int >get_divisors (int n){ vector<int > res; for (int i = 1 ; i <= n / i; i++){ if (!(n % i)){ res.emplace_back (i); if (i != n / i) res.emplace_back (n / i); } } sort (res.begin (), res.end ()); return res; }
算数基本定理 任意自然数可唯一分解$N=\prod\limits_{i=1}^ka_i^k$
约数个数$\prod\limits_{i=1}^k(a_i+1)$ 约数之和$\prod\limits_{i=1}^k\sum\limits_{j=0}^{a_i}a^j$
欧几里得算法 1 int gcd (int a, int b) {return b ? gcd (b, a % b) : a;}
欧拉函数 欧拉函数:1到n中与n互质的数的个数
$\phi(n)=N(1-\prod\frac 1{p_i})$
筛法求欧拉函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 void get_phi (int n) { phi[1 ] = 1 ; for (int i = 2 ; i <= n; i++){ if (!comp[i]) { primes[cnt++] = i; phi[i] = i - 1 ; } for (int j = 0 ; primes[j] <= n / i; j++){ comp[i * primes[j]] = 1 ; if (!(i % primes[j])){ phi[i * primes[j]] = primes[j] * phi[i]; break ; } phi[i * primes[j]] = (primes[j] - 1 ) * phi[i]; } } }
三个转移情况:
i 为质数时$\phi(i)=i-1$
i 整除primes[ j ]时,$\phi(i*primes[j]) = primes[j] \times \phi(i)$
i 不整除primes[ j ]时,$\phi(i*primes[j]) = (primes[j]-1) \times \phi(i)$
欧拉定理 $$ 若a与n互质,则有a^{\phi(n)}\equiv 1(mod\ n) $$
快速幂 1 2 3 4 5 6 7 8 9 int qmi (int a, int k, int p) { int res = 1 ; while (k){ if (k & 1 ) res = res * a % p; k >>= 1 ; a = a * a % p; } return res; }
$O(\log(k))$ 的时间复杂度计算$a^k\ %p$
扩展欧几里得算法 1 2 3 4 5 6 7 8 9 int exgcd (int a, int b, int &x, int &y) { if (!b){ x = 1 , y = 0 ; return a; } int d = exgcd (b, a % b, y, x); y -= a / b * x; return d; }
动态规划 以下v表示volume, w表示worth
01背包 1 2 3 4 5 for (int i = 1 ; i <= n ;i++){ for (int j = V; j >= v[i]; j--){ dp[j] = max (dp[j], dp[j - v[i]] + w[i]); } }
完全背包 1 2 3 4 5 for (int i = 1 ; i <= N; i++){ for (int j = v[i]; j <= V; j++){ dp[j] = max (dp[j], dp[j - v[i]] + w[i]); } }
多重背包 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 int cnt = 0 ;for (int i = 1 ; i <= n; i++){ int a, b, s; cin >> a >> b >> s; int k = 1 ; while (k <= s){ cnt++; v[cnt] = a * k; w[cnt] = b * k; s -= k; k *= 2 ; } if (s > 0 ){ cnt++; v[cnt] = a * s; w[cnt] = b * s; } } n = cnt; for (int i = 1 ; i <= n; i++){ for (int j = V; j >= v[i]; j--){ dp[j] = max (dp[j], dp[j - v[i]] + w[i]); } }
分组背包 1 2 3 4 5 6 7 8 9 for (int i = 1 ; i <= n; i++){ for (int j = V; j >= 0 ; j--){ for (int k = 0 ; k < s[i]; k++){ if (v[i][k] <= j){ dp[j] = max (dp[j], dp[j - v[i][k]] + w[i][k]); } } } }
多维费用背包问题 简单加循环即可。