0-板子合集
Tricks
取整
通用
离散加小步长
需要单调
向下取整和向上取整
向下取整是闭开
向上取整是开闭
和在整数域上由于向下取整和向上取整对应的区间中总是在同一直线,同时向下取整的左端点总是小于向上取整的左端点,向下取整的右端点总是大于向上取整的右端点,故两者在整数域上等效,同时
中点
中点m取n的时候,偶数n,m在偏右,如遍历左边取到m的话,奇偶都是左边偏大,取n - 1的时候,奇数左偏大,偶数左右相等
中点m由n时,
- 偶数n,m偏右中点,遍历左边取m的话,左偏大,不取的话,一样大。
- 奇数n,m不偏不倚,遍历左边取m的话,左偏大,不取的话,右偏大
取的话
左[l, m],右[m + 1, r]
不取的话
左[l, m - 1], 右[m, r]
中点m由n-1时
- 偶数n,m偏左中点,遍历左边取m的话,一样大,不取的话,右偏大
- 奇数n,m不偏不倚,遍历左边取m的话,左偏大,不取的话,右偏大
取的话
左[l, m],右[m + 1, r]
不取的话
左[l, m - 1],右[m, r]
和中点m由n时的取不取情况是镜像的,但是表达式只取决于左边取不取中点
能把映射到
杂项
区间合并
using pii = pair<int, int>;vector<pii> a, res;
sort(a.begin(), a.end());auto [st, ed] = a[0];for (int i = 1; i < a.size(); ++i) { auto [st1, ed1] = a[i]; if (st1 <= ed) { ed = max(ed, ed1); } else { res.push_back({st, ed}); st = st1; ed = ed1; }}res.push_back({st, ed});离散化
哈希
struct discretization { unordered_map<string, int> m;
discretization(const vector<string>& a) { for (int i = 0; i < a.size(); ++i) { m[a[i]] = i; } }
int operator[](string x) { return m[x]; }} dt;二分
struct discretization { vector<int> help;
discretization(const vector<int>& a) : help(a) { sort(help.begin(), help.end()); help.erase(unique(help.begin(), help.end())); }
int operator[](int x) { return ( lower_bound(help.begin(), help.end(), x) - help.begin() ); }}排序
归并排序
#define N 1000
int tmp[N];void ms(int q[], int l, int r) { if (l >= r) return;
int m = l + r >> 1;
ms(q, l, m), ms(q, m + 1, r);
int i = l, j = m + 1, k = 0; while (i <= m && j <= r) { if (q[i] <= q[j]) tmp[k++] = q[i++]; else tmp[k++] = q[j++];// 这里可以统计以q[j]为结尾的逆序对数量,全部加起来就是逆序对数量 }
while (i <= m) tmp[k++] = q[i++]; while (j <= r) tmp[k++] = q[j++];
for (int i = l, j = 0; i <= r; ++i) q[i] = tmp[j++];}查找
| 大小序 | 条件 | 位置 | 过程 | |
|---|---|---|---|---|
| 升序+ | >= target+ || > target+ | 第一个数 | (0n, j取等,中点偏左) | |
| 升序+ | <= target- || < target- | 最后一个数 | (-1n-1, i取等,中点偏右) | |
| 降序- | <= target- || < target- | 第一个数 | (0n, j取等,中点偏左) | |
| 降序- | >= target+ || > target+ | 最后一个数 | (-1n-1, i取等,中点偏右) |
#include <iostream>#include <vector>
using namespace std;
int binarySearch(vector<int> arr, int target) { int i = 0, j = arr.size();
while (i < j) { int m = (i + j) >> 1;
if (arr[m] > target) { j = m; } else { i = m + 1; } }
return j;}抽象代数
| 结构 | 结构性质 | 逆运算 |
|---|---|---|
| ,Z | 阿贝尔群 | |
| ,Z | 阿贝尔群 | |
| ,R | 阿贝尔群 | - |
| ,R/{0} | 阿贝尔群 | / |
| & | 半格(无逆元的交换群) | |
| | | 半格(无逆元的交换群) | |
| max | 半格(无逆元的交换群) | |
| min | 半格(无逆元的交换群) | |
| ,所有矩阵,即矩阵加法 | 阿贝尔群 | - |
| ,所有矩阵,即矩阵乘法 | 幺半群 |
和同或有的关系
& 和 |和max和min
&
单位元:-1
性质
|
单位元:0
性质
max
单位元:LLONG_MAX
min
单位元:LLONG_MIN
高精度
高 + 高
// 高 + 高vector<int> add(vector<int> a, vector<int> b) { if (a.size() < b.size()) return add(b, a);
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.emplace_back(t % 10); t /= 10; }
if (t) c.emplace_back(t); return c;}高 - 高
// 高 - 高vector<int> sub(vector<int> a, vector<int> 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.emplace_back((t + 10) % 10); if (t < 0) t = -1; }
while (c.size() > 1 && c.back() == 0) c.pop_back();
return c;}高 * 低
// 高 * 低vector<int> mul(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; c.emplace_back(t % 10); t /= 10; }
while (c.size() > 1 && c.back() == 0) c.pop_back(); return c;}高 / 低
// 高 / 低vector<int> div(vector<int> a, int b) { vector<int> c;
int r = 0; for (int i = a.size() - 1; i >= 0; --i) { r = r * 10 + a[i]; c.emplace_back(r / b); r %= b; }
reverse(c.begin(), c.end()); while (c.size() > 1 && c.back() == 0) c.pop_back(); return c;}高 * 高
vector<int> mul(vector<int>& a, vector<int>& b) { vector<int> c(a.size() + b.size(), 0);
for (int i = 0; i < a.size(); ++i) { for (int j = 0; j < b.size(); ++j) { c[i + j] += a[i] * b[j]; } }
int t = 0; for (int i = 0; i < c.size(); ++i) { t += c[i]; c[i] = t % 10; t /= 10; }
while (c.size() > 1 && c.back() == 0) c.pop_back(); return c;}高 / 高
仅仅适合小数据
bool cmp(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]; return true;}
vector<int> div(vector<int>& a, vector<int>& b, vector<int>& r) { vector<int> c; r.clear();
for (int i = a.size() - 1; i >= 0; --i) { r.insert(r.begin(), a[i]);
while (r.size() > 1 && r.back() == 0) r.pop_back();
int x = 0; while (cmp(r, b)) { r = sub(r, b); x++; }
c.push_back(x); }
reverse(c.begin(), c.end()); while (c.size() > 1 && c.back() == 0) c.pop_back();
return c;}多项式
基础约定
// a[i] 表示 x^i 的系数(低位在前)using poly = vector<int>;
const int mod = 998244353;const int G = 3;加法
poly add(poly a, poly b) { if (a.size() < b.size()) swap(a, b); for (int i = 0; i < b.size(); ++i) a[i] = (a[i] + b[i]) % mod; return a;}减法
poly sub(poly a, poly b) { if (a.size() < b.size()) a.resize(b.size()); for (int i = 0; i < b.size(); ++i) a[i] = (a[i] - b[i] + mod) % mod;
while (a.size() > 1 && a.back() == 0) a.pop_back(); return a;}朴素乘法
poly mul(poly a, poly b) { poly c(a.size() + b.size() - 1);
for (int i = 0; i < a.size(); ++i) for (int j = 0; j < b.size(); ++j) c[i + j] = (c[i + j] + 1LL * a[i] * b[j]) % mod;
return c;}NTT乘除法
快速幂
int qpow(int a, int b) { int res = 1; while (b) { if (b & 1) res = 1LL * res * a % mod; a = 1LL * a * a % mod; b >>= 1; } return res;}
int inv(int x) { return qpow(x, mod - 2);}核心
void ntt(poly &a, bool invert) { int n = a.size(); static vector<int> rev; static vector<int> roots{0, 1};
if ((int)rev.size() != n) { int k = __builtin_ctz(n); rev.resize(n); for (int i = 0; i < n; ++i) rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (k - 1)); }
if ((int)roots.size() < n) { int k = __builtin_ctz(roots.size()); roots.resize(n); while ((1 << k) < n) { int e = qpow(G, (mod - 1) >> (k + 1)); for (int i = 1 << (k - 1); i < (1 << k); ++i) { roots[2 * i] = roots[i]; roots[2 * i + 1] = 1LL * roots[i] * e % mod; } k++; } }
for (int i = 0; i < n; ++i) if (i < rev[i]) swap(a[i], a[rev[i]]);
for (int len = 1; len < n; len <<= 1) { for (int i = 0; i < n; i += 2 * len) { for (int j = 0; j < len; ++j) { int u = a[i + j]; int v = 1LL * a[i + j + len] * roots[len + j] % mod; a[i + j] = (u + v) % mod; a[i + j + len] = (u - v + mod) % mod; } } }
if (invert) { reverse(a.begin() + 1, a.end()); int inv_n = inv(n); for (int &x : a) x = 1LL * x * inv_n % mod; }}NTT乘法
poly mul_ntt(poly a, poly b) { int n = 1; while (n < a.size() + b.size()) n <<= 1;
a.resize(n); b.resize(n);
ntt(a, false); ntt(b, false);
for (int i = 0; i < n; ++i) a[i] = 1LL * a[i] * b[i] % mod;
ntt(a, true);
return a;}NTT带余除法
poly rev(poly a) { reverse(a.begin(), a.end()); return a;}
poly poly_div(poly a, poly b) { int n = a.size() - 1; int m = b.size() - 1;
if (n < m) return {0};
int k = n - m + 1;
poly ra = rev(a); poly rb = rev(b);
rb.resize(k);
poly inv_rb = poly_inv(rb, k);
poly rq = mul_ntt(ra, inv_rb); rq.resize(k);
poly q = rev(rq); return q;}
poly poly_mod(poly a, poly b) { poly q = poly_div(a, b); poly prod = mul_ntt(b, q);
poly r = sub(a, prod);
r.resize(b.size() - 1);
while (r.size() > 1 && r.back() == 0) r.pop_back(); return r;}
pair<poly, poly> poly_divmod(poly a, poly b) { poly q = poly_div(a, b); poly r = poly_mod(a, b); return {q, r};}乘法逆元
a[0] != 0
poly poly_inv(poly a, int n) { if (n == 1) return {inv(a[0])};
poly b = poly_inv(a, (n + 1) >> 1);
int size = 1; while (size < 2 * n) size <<= 1;
poly tmp = a; tmp.resize(size); b.resize(size);
ntt(tmp, false); ntt(b, false);
for (int i = 0; i < size; ++i) b[i] = 1LL * b[i] * (2 - 1LL * tmp[i] * b[i] % mod + mod) % mod;
ntt(b, true); b.resize(n);
return b;}导数
poly deriv(poly a) { if (a.size() == 1) return {0}; for (int i = 1; i < a.size(); ++i) a[i - 1] = 1LL * a[i] * i % mod; a.pop_back(); return a;}积分
poly integ(poly a) { a.push_back(0); for (int i = a.size() - 1; i > 0; --i) a[i] = 1LL * a[i - 1] * inv(i) % mod; a[0] = 0; return a;}多项式ln
poly poly_ln(poly a, int n) { poly da = deriv(a); poly inva = poly_inv(a, n);
poly res = mul_ntt(da, inva); res.resize(n - 1);
return integ(res);}多项式exp
poly poly_exp(poly a, int n) { if (n == 1) return {1};
poly b = poly_exp(a, (n + 1) >> 1);
poly ln_b = poly_ln(b, n);
poly diff = sub(a, ln_b); diff[0] = (diff[0] + 1) % mod;
b = mul_ntt(b, diff); b.resize(n);
return b;}多项式快速幂
poly poly_pow(poly a, int k, int n) { poly ln_a = poly_ln(a, n);
for (int &x : ln_a) x = 1LL * x * k % mod;
return poly_exp(ln_a, n);}线性基
普通
struct LinearBasis { static const int LOG = 60; // long long
long long d[LOG + 1]{};
// 插入一个数 void insert(long long x) { for (int i = LOG; i >= 0; --i) { if (!(x >> i & 1)) continue;
if (!d[i]) { d[i] = x; return; }
x ^= d[i]; } }
// 查询最大异或值 long long query_max() { long long res = 0; for (int i = LOG; i >= 0; --i) res = max(res, res ^ d[i]); return res; }};可求第k小
struct LinearBasis { static const int LOG = 60; long long d[LOG + 1]{}; vector<long long> p; // 重构后的基
void insert(long long x) { for (int i = LOG; i >= 0; --i) { if (!(x >> i & 1)) continue; if (!d[i]) { d[i] = x; return; } x ^= d[i]; } }
// 重构(变成上三角) void rebuild() { for (int i = 0; i <= LOG; ++i) for (int j = 0; j < i; ++j) if (d[i] >> j & 1) d[i] ^= d[j];
for (int i = 0; i <= LOG; ++i) if (d[i]) p.push_back(d[i]); }
// 第 k 小(0-based) long long kth(long long k) { if (k >= (1LL << p.size())) return -1;
long long res = 0; for (int i = 0; i < p.size(); ++i) if (k >> i & 1) res ^= p[i]; return res; }};高斯消元
浮点数
const int N = 110;const double eps = 1e-8;
double a[N][N]; // 增广矩阵 n 行 n+1 列
// 返回值:// 0 = 无解// 1 = 唯一解// 2 = 无穷多解int gauss(int n) { int r = 0, c = 0;
while (c < n) { // 1️ 找主元(绝对值最大) int t = r; for (int i = r; i < n; ++i) if (fabs(a[i][c]) > fabs(a[t][c])) t = i;
// 2️ 这一列全是0 → 自由变量 if (fabs(a[t][c]) < eps) { c++; continue; }
// 3️ 换行 for (int j = c; j <= n; ++j) swap(a[t][j], a[r][j]);
// 4️ 归一化 double div = a[r][c]; for (int j = c; j <= n; ++j) a[r][j] /= div;
// 5️ 消元(上下都消) for (int i = 0; i < n; ++i) { if (i != r && fabs(a[i][c]) > eps) { double factor = a[i][c]; for (int j = c; j <= n; ++j) a[i][j] -= factor * a[r][j]; } }
r++, c++; }
// 6️ 判断解的情况 for (int i = r; i < n; ++i) if (fabs(a[i][n]) > eps) return 0; // 无解
return r < n ? 2 : 1;}模2高斯消元
const int N = 110;
int a[N][N]; // n 行 n+1 列,值为 0/1
// 返回:0无解,1唯一解,2多解int gauss_xor(int n) { int r = 0;
for (int c = 0; c < n; ++c) { int t = r; for (int i = r; i < n; ++i) if (a[i][c]) { t = i; break; }
if (!a[t][c]) continue;
for (int j = c; j <= n; ++j) swap(a[t][j], a[r][j]);
for (int i = 0; i < n; ++i) if (i != r && a[i][c]) for (int j = c; j <= n; ++j) a[i][j] ^= a[r][j];
r++; }
for (int i = r; i < n; ++i) if (a[i][n]) return 0;
return r < n ? 2 : 1;}位运算
格雷码
正变换
int get(int x) { return x ^ (x >> 1);}逆变换
int x = g;for (; g; g >>= 1) x ^= g;return x;常用函数
bit_width(x) = 1 + floor(log2(x))
int bit_width(unsigned x) { int res = 0; while (x) { ++res; x >>= 1; } return res;}countr_zero(x) = bit_width(lowbit(x)) - 1
int countr_zero(unsigned x) { if (x == 0) return 32; return bit_width(x & -x) - 1;}组合数学
组合
class Solution {public: vector<vector<int>> combine(int n, int k) { vector<vector<int>> ans;
vector<int> unit; auto dfs = [&](auto&& dfs, int elem) { int limit = k - unit.size(); if (limit == 0) { return; }
unit.push_back(elem);
if (limit == 1) { ans.push_back(unit); }
for (int index = elem + 1; index <= n; ++index) { dfs(dfs, index); }
unit.pop_back(); };
for (int i = 1; i <= n; ++i) { dfs(dfs, i); }
return ans; }};排列
负数组合
Lucas定理
p为素数,其余为自然数
实现
#include <bits/stdc++.h>using namespace std;using ll = long long;const mod N = 1e5;const ll mod = 998244353;//其实没什么用,lucas只能用于小质数模数ll fact[N], ifact[N];
ll qp(ll a, ll b, ll p) { ll res = 1; while (b) { if (b & 1) res = res * a % p; a = a * a % p; b >>= 1; } return res;}
void init(int n) { fact[0] = 1; for (int i = 1; i <= n; ++i) fact[i] = i * fact[i - 1] % mod; for (int i = 0; i <= n; ++i) ifact[i] = qp(fact[i], mod - 2, mod);}
ll c(ll n, ll k) { if (k > n) return 0; return fact[n] % mod * ifact[k] % mod * ifact[n - k] % mod;}
ll lucas(ll n, ll k) { if (!k) return 1; return c(n % mod, k % mod) * lucas(n / mod, k / mod) % mod;}矩阵快速幂
将快速幂思想迁移到递推式
以斐波那契数列为例
使用矩阵快速幂的前提是所求值可化为上一列各元素的线性组合,需要是常数,以为例有
using ll = long long;
vector<vector<ll>> mul(const vector<vector<ll>>& a, const vector<vector<ll>> b, ll mod) { int n = a.size(), mid = a[0].size(), m = b[0].size();
vector<vector<ll>> res(n, vector<ll>(m, 0));
for (int i = 0; i < n; ++i) { for (int j = 0; j < m; ++j) { for (int k = 0; k < mid; ++k) { res[i][j] = (res[i][j] + a[i][k] * b[k][j]) % mod; } } }
return res;}
vector<vector<ll>> mmi(vector<vector<ll>> A, ll b, ll mod) { vector<vector<ll>> res = {{1 % mod, 0}, {0, 1 % mod}}; while (b) { if (b & 1) res = mul(res, A, mod); A = mul(A, A, mod); b >>= 1; } return res;}对于特殊的前缀和,也可以用矩阵快速幂进行计算,以
为例,相对于需要多留一行给求和积累效果
总结来说,矩阵快速幂有以下几个值得注意的点
- 前缀和只有其本身和组成单位有关,权为1
- 等比数列只和其所在列有关,权为q
- 和其所在列和对应和等比数列有关,权都为q
- 等差数列可看作常数C的前缀和
可见,矩阵快速幂对若干个等差数列和等比数列的线性组合和前缀和都适用,其他并不适用
矩阵快速幂如果用二维vector或者struct自行实现会被卡常,需要转化为维护变换的几个值和ans的几个值,
这里上三角矩阵的幂仍然是上三角矩阵,故变换可等价为右上角的6个值的递归,由于对角线独立幂化,故最左上角的1为定值1
下三角矩阵和上三角矩阵性质相同,而反上下三角矩阵性质则大有不同
数论
中国剩余定理(CRT)
扩展CRT
using ll = long long;
// 扩展gcdll exgcd(ll a, ll b, ll &x, ll &y) { if (!b) { x = 1; y = 0; return a; } ll x1, y1; ll g = exgcd(b, a % b, x1, y1); x = y1; y = x1 - a / b * y1; return g;}
// 扩展CRT// a[i] ≡ x (mod m[i])// 返回 (x, lcm) 或 (-1, -1) 表示无解pair<ll, ll> excrt(vector<ll>& a, vector<ll>& m) { ll A = a[0], M = m[0];
for (int i = 1; i < a.size(); ++i) { ll a2 = a[i], m2 = m[i];
ll x, y; ll g = exgcd(M, m2, x, y);
ll c = a2 - A; if (c % g != 0) return {-1, -1}; // 无解
// 防溢出写法 ll mod = m2 / g; x = (__int128)x * (c / g) % mod;
A = A + x * M; M = M / g * m2;
A = (A % M + M) % M; }
return {A, M};}快速乘
ll qmul(ll a, ll b, ll p) { ll q = (ld)a / p * b; ll res = (ull)a * b - (ull)q * p; return (res + p) % p;}扩展欧几里得
pair<int, int> extgcd(int a, int b) { if (!b) return make_pair(1, 0); auto [s, t] = extgcd(b, a % b); return make_pair(t, s - a / b * t);//t * (1,s/t-a/b)}质数
筛法
线性筛法
int primes[N], idx;bool vis[N];void getp(int n) { memset(primes, 0, sizeof primes); primes[1] = 0; for (int i = 2; i <= n; ++i) { if (!vis[i]) primes[++idx] = i; for (int j = 1; i * primes[j] <= n; ++j) { vis[i * primes[j]] = 1; if (i % primes[j] == 0) break; } }}埃筛
int primes[N], idx;bool vis[N];void getp(int n) { for (int i = 2; i <= n; ++i) { if (vis[i]) continue; primes[idx++] = i; for (int j = i; j <= n / j; ++j) { vis[j * i] = 1; } }}约数
质因数分解
纯暴力
unordered_map<int, int> pcnt;
void getfactor(int n) { for (int i = 2; i <= n / i; ++i) { while (n % i == 0) { ++pcnt[i]; n /= i; } }}阶乘的质因数分解
约数计算
欧拉函数
性质
图论
基础约定
using PII = pair<int, int>;
const int INF = 0x3f3f3f3f;const int N = 1e3;
int h[N], e[N], ne[N], idx, w[N];int g[N][N];int d[N][N];void initGraph() { memset(h, -1, sizeof h); idx = 0;}
// 无向图struct Edge { int a, b, w; bool operator < (const Edge &W) const { return w < W.w; }} edges[N];
// 最短路int dist[N];bool st[N];最短路
单源最短路
Dijkstra
- 朴素
// 朴素Dijkstraint dijkstra(int s, int n) { memset(dist, 0x3f, sizeof dist); dist[s] = 0;
for (int k = 0; k < n - 1; ++k) { int t = 0; for (int i = 1; i <= n; ++i) if (!st[i] && (!t || dist[i] < dist[t])) t = i; for (int i = 1; i <= n; ++i) dist[i] = min(dist[i], dist[t] + g[t][i]); st[t] = 1; }
return dist[n] == INF ? -1 : dist[n];}- 堆优化
// 堆优化Dijkstraint Dijkstra(int s, int n) { memset(dist, 0x3f, sizeof dist); dist[s] = 0;
priority_queue<PII, vector<PII>, greater<PII>> q; q.push({0, s});
while(q.size()) { PII t = q.top(); int ver = t.second, dis = t.first; if (st[ver]) continue;st[ver] = 1;
for (int i = h[ver]; i != -1; i = ne[i]) { int j = e[i]; if (dist[j] > dist[ver] + w[i]) { dist[j] = dist[ver] + w[i]; q.push({dist[j], j}); } } }
return dist[n] == INF ? -1 : dist[n];}Bellmanford
- 朴素
// Bellmanfordint Bellmanford(int s, int n, int m) { memset(dist, 0x3f, sizeof dist); dist[s] = 0;
for (int k = 0; k < n; ++k) { for (int i = 1; i <= n; ++i) { int a = i; for (int j = h[i]; j != -1; j = ne[j]) { int b = e[j]; dist[b] = min(dist[b], dist[a] + w[j]); } } }
return dist[n] > dist[n] >> 1 ? -1 : dist[n];}- SPFA
#include <bits/stdc++.h>using namespace std;
using pii = pair<int, int>;const int INF = 0x3f3f3f3f;const int N = 100005;
vector<pii> g[N]; // {to, weight}int dist[N];bool inq[N]; // 是否在队列中int cnt[N]; // 入队次数(用于判负环)
// 返回:true 表示无负环,false 表示存在负环bool spfa(int n, int s) { memset(dist, 0x3f, sizeof dist); memset(inq, 0, sizeof inq); memset(cnt, 0, sizeof cnt);
queue<int> q; dist[s] = 0;
q.push(s); inq[s] = true; cnt[s] = 1;
while (!q.empty()) { int u = q.front(); q.pop(); inq[u] = false;
for (auto [v, w] : g[u]) { if (dist[v] > dist[u] + w) { dist[v] = dist[u] + w;
if (!inq[v]) { q.push(v); inq[v] = true; cnt[v]++;
// 🚨 负环判断 if (cnt[v] >= n) return false; } } } }
return true;}多源最短路
Floyd
// Floydvoid floyd(int n) { 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]); } } }}最小生成树
Prim
int prim() { memset(dist, 0x3f, sizeof dist); memset(st, 0, sizeof st);
dist[1] = 0; int res = 0;
for (int i = 0; i < n; ++i) { int t = -1; for (int j = 1; j <= n; ++j) if (!st[j] && (t == -1 || dist[j] < dist[t])) t = j;
if (dist[t] == INF) return INF;
res += dist[t]; st[t] = 1;
for (int j = 1; j <= n; ++j) dist[j] = min(dist[j], g[t][j]); }
return res;}Kruscal
struct Edge { int a, b, w; bool operator<(const Edge& other) const { return w < other.w; }};
int p[N];
int find(int x) { if (p[x] != x) p[x] = find(p[x]); return p[x];}
int kruskal() { for (int i = 1; i <= n; ++i) p[i] = i;
sort(ed, ed + m);
int res = 0, cnt = 0;
for (int i = 0; i < m; ++i) { int a = ed[i].a, b = ed[i].b, w = ed[i].w;
int pa = find(a), pb = find(b);
if (pa != pb) { p[pa] = pb; res += w; cnt++; } }
return cnt == n - 1 ? res : INF;}Tarjan
求强连通分量
const int N = 100005;
vector<int> g[N];
int dfn[N], low[N], timestamp;int stk[N], top;bool in_stk[N];
int scc_id[N], scc_cnt;
void tarjan(int u) { dfn[u] = low[u] = ++timestamp; stk[++top] = u; in_stk[u] = true;
for (int 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]); } }
// 找到一个 SCC if (dfn[u] == low[u]) { ++scc_cnt; int y; do { y = stk[top--]; in_stk[y] = false; scc_id[y] = scc_cnt; } while (y != u); }}Tarjan 求割点(无向图)
vector<int> g[N];int dfn[N], low[N], timestamp;bool is_cut[N];
void tarjan(int u, int parent) { dfn[u] = low[u] = ++timestamp;
int child = 0;
for (int v : g[u]) { if (!dfn[v]) { tarjan(v, u); low[u] = min(low[u], low[v]);
// 割点判断 if (parent != -1 && low[v] >= dfn[u]) is_cut[u] = true;
child++; } else if (v != parent) { low[u] = min(low[u], dfn[v]); } }
// 根节点特殊判断 if (parent == -1 && child > 1) is_cut[u] = true;}求桥
struct Edge { int to, id;};
vector<Edge> g[N];int dfn[N], low[N], timestamp;bool is_bridge[N];
void tarjan(int u, int in_edge) { dfn[u] = low[u] = ++timestamp;
for (auto [v, id] : g[u]) { if (!dfn[v]) { tarjan(v, id); low[u] = min(low[u], low[v]);
// 桥判断 if (low[v] > dfn[u]) is_bridge[id] = true; } else if (id != in_edge) { low[u] = min(low[u], dfn[v]); } }}动态规划
线性DP
有两种,最大子数组和这种求连续区间的,和最长上升子序列和最长公共子序列这种求序列的
最长公共子序列
对于字符串s,t
假如s[i] == s[j], 有
都不选 = dfs(i - 1, j - 1)
不选s = dfs(i - 1, j),t是不确定的
不选t = dfs(i, j - 1),s是不确定的
不选s,假如后续也不选t了则应有dfs(i - 1, j) =dfs(i - 1, j - 1) < 都选,假如后续选了t则dfs(i - 1, j ) = dfs(i - k, j - 1) + 1,由于子串i - k,j - 1是i - 1, j -1的子串,故dfs(i - k, j - 1) <= x,dfs(i - 1, j) <= x + 1,故不需考虑不选其中1个的情况
背包DP
01背包
| 类型 | 求最大值 | 求最小值 |
|---|---|---|
| 至多 (≤ C) | f[0..C] = 0 | 一般不常见(不选就是0,已最小) |
| 恰好 (= C) | f[0] = 0,其余 -∞ | f[0] = 0,其余 +∞ |
| 至少 (≥ C) | 不常见 | f[0] = 0,其余 +∞ |
数位DP
单上界数位dp
一般用于计数算1-n,若是区间[l, r]则通过下列公式计算
区间数位dp
#include <bits/stdc++.h>
using namespace std;using ll = long long;using ull = unsigned long long;using ld = long double;
ll p10[16];ll memo[20][20];bool vis[20][20];string lowS, highS;int n;
ll dfs(int i, bool tl, bool tr, int k) { if (i == n) return 0; if (!tl && !tr && vis[i][k]) return memo[i][k];
int lo = tl ? (lowS[i] - '0') : 0; int hi = tr ? (highS[i] - '0') : 9;
ll best = -1; for (int d = lo; d <= hi; ++d) { bool ntl = tl && (d == lowS[i] - '0'); bool ntr = tr && (d == highS[i] - '0'); ll cur; if (!k && !d) { cur = dfs(i + 1, ntl, ntr, 0); } else cur = 1LL * d * p10[k] + dfs(i + 1, ntl, ntr, k + 1);
best = max(best, cur); }
if (!tl && !tr) { vis[i][k] = 1; memo[i][k] = best; } return best;}
int main() { ios::sync_with_stdio(false);cin.tie(nullptr); int t;cin>>t; p10[0] = 1; for (int i = 1; i < 15; ++i) p10[i] = 10 * p10[i - 1];
while(t--) { ll l,r;cin>>l>>r; highS = to_string(r); string tmp = to_string(l); lowS = string(highS.size() - tmp.size(), '0') + tmp; memset(vis, 0, sizeof vis); n = highS.size();
ll ans = dfs(0, 1, 1, 0); cout << ans << '\n'; }}对于tl = 1, tr = 1的状态只有一条路径转移路径通往该状态,所以不用缓存,tl = 0, tr = 0的状态有多条路径,所以需要缓存避免重复计算。
| 情况 | is_num 放不放进 memo? |
|---|---|
| 有 mask/k 等变量能区分开没开始 | 不用放(已被隐含编码) |
| 没有其他变量区分,且两种都缓存 | 必须放(否则值混淆) |
| 没有其他变量区分,但只缓存 is_num=true | 不用放(false 只走一次,不缓存) |
| 问题类型 | 前导零有害? | 需要 is_num? |
|---|---|---|
| 数位之和 | 无害(贡献 0) | 不需要 |
| 是否含某数字 | 无害(0 不碍事) | 不需要 |
| 数位互不相同 | 有害(假装用了 0) | 需要 |
| 相邻/递增/递减 | 有害(假装有前一位) | 需要 |
| 首位特殊约束 | 有害(谁是首位?) | 需要 |
| 位权/翻转相关 | 有害(位权偏移) | 需要(甚至要 k) |
数据结构
单调栈
找左右第一个小于的是递增栈,小于号朝向欲push的元素,大于的是递减栈,大于号朝向欲push的元素
- 左边第一个,从左到右top幸存者是淘汰者的答案,意味着下一层是其上一层的答案,从右到左淘汰者是top被淘汰者的答案,
- 右边第一个,从右到左top幸存者是淘汰者的答案,意味着下一层是其上一层的答案,从左到右淘汰者是top被淘汰者的答案
同向幸存者,反向淘汰者
Manacher
预处理后的串索引中心idx映射到原串的字符串区间:
原回文串长度 = 回文半径 - 1
void solve(string s) { int n = s.size();
string str(2 * n + 3, '#'); str[0] = '$'; for (int i = 0; i < n; ++i) { str[2 * i + 2] = s[i]; } str[2 * n + 2] = '^';
vector<int> plen(2 * n + 3); int boxR = 0, boxM = 0; for (int i = 2; i < 2 * n + 1; ++i) { int pl = 1;
if (i < boxR) { pl = min(plen[2 * boxM - i], boxR - i); }
while (str[i - pl] == str[i + pl]) { ++pl; }
if (i + pl > boxR) { boxR = i + pl; boxM = i; }
// 求最长回文子串,或者回文子串数量
plen[i] = pl; }}莫队
参考
```cpp#include <bits/stdc++.h>
using namespace std;using ll = long long;
const int N = 50000 + 1;
int n, m;int col[N];int block;int cnt[N];ll ans1[N], ans2[N];ll sum;
struct query { ll l, r, id;
bool operator<(const query& q) const { int bl = l / block; int br = q.l / block; if (bl != br) return bl < br; if (bl & 1) return r < q.r; return r > q.r; }
} a[N];
ll gcd(ll a, ll b) { return b ? gcd(b, a % b) : a;}
void add(int x) { sum += cnt[col[x]]; ++cnt[col[x]];}
void del(int x) { sum -= (cnt[col[x]] - 1); --cnt[col[x]];}
void solve() { cin >> n >> m; if (m == 0) return;
block = int(n / sqrt(m)) + 1;
for (int i = 1; i <= n; ++i) cin >> col[i];
for (int i = 0; i < m; ++i) { cin >> a[i].l >> a[i].r; a[i].id = i; } sort(a, a + m);
for (ll i = 0, l = 1, r = 0; i < m; ++i) { query& q = a[i]; while (l > q.l) add(--l); while (r < q.r) add(++r); while (l < q.l) del(l++); while (r > q.r) del(r--); ans1[q.id] = sum; ans2[q.id] = (r - l + 1) * (r - l) / 2; }
for (int i = 0; i < m; ++i) { if (ans1[i] && ans2[i]) { ll d = gcd(ans1[i], ans2[i]); cout << ans1[i] / d << '/' << ans2[i] / d << '\n'; } else { cout << 0 << '/' << 1 << '\n'; } }}
int main() { cin.tie(nullptr)->sync_with_stdio(false); solve();}普通线段树
```c++class SegmentTree { int n; vector<int> tr;
// 区间业务 int merge_val(int a, int b) const { return max(a, b); }
// 维护结点 void maintain(int node) { tr[node] = merge_val(tr[node * 2], tr[node * 2 + 1]); }
// 建树 void build(int node, int l, int r, const vector<int>& a) { if (l == r) { tr[node] = a[l]; return; }
int m = l + r >> 1; build(node * 2, l, m, a); build(node * 2 + 1, m + 1, r, a); maintain(node); }
// 单点更新 void update(int node, int l, int r, int i, int val) { if (l == r) { tr[node] = merge_val(tr[node], val); return; }
int m = l + r >> 1; if (i <= m) { update(node * 2, l, m, i, val); } else { update(node * 2 + 1, m + 1, r, i, val); } maintain(node); }
// 区间查询 int query(int node, int l, int r, int ql, int qr) const { if (ql <= l && r <= qr) { return tr[node]; }
int m = l + r >> 1; if (qr <= m) { return query(node * 2, l, m, ql, qr); } if (ql > m) { return query(node * 2 + 1, m + 1, r, ql, qr); } int l_res = query(node * 2, l, m, ql, qr); int r_res = query(node * 2 + 1, m + 1, r, ql, qr); return merge_val(l_res, r_res); }
// 查找查找区间最左边的某值 int find(int node, int l, int r, int ql, int qr, int val) const { if (ql > r || qr < l) return -1; // if (业务剪枝) return -1; if (l == r && tr[node] == val) return l;
int m = l + r >> 1; int l_res = find(node * 2, l, m, ql, qr, val); if (l_res != -1) return l_res; return find(node * 2 + 1, m + 1, r, ql, qr, val); }
public:
SegmentTree(const vector<int>& a) : n(a.size()), tr(2 << bit_width(n - 1)) { build(1, 0, n - 1, a); }
void update(int i, int val) { update(1, 0, n - 1, i, val); }
int query(int ql, int qr) { return query(1, 0, n - 1, ql, qr); }
int find(int ql, int qr, int val) const { return find(1, 0, n - 1, ql, qr, val); }};树状数组
class FenwickTree {public: int n; vector<int> tree;
FenwickTree(const vector<int>& a) : n((int)a.size() - 1) { tree = a; for (int i = 1; i <= n; ++i) { // 更新父结点的值 int j = i + (i & -i); if (j <= n) tree[j] += tree[i]; } }
// 前缀和 int presum(int i) { int s = 0; while (i > 0) { s += tree[i]; i &= i - 1; } return s; }
// 单点更新 void update(int i, int delta) { while (i <= n) { tree[i] += delta; i += i & -i; } }
// 区间和 int query(int l, int r) { return presum(r) - presum(l - 1); }};Trie树
普通Trie
int son[N][26], cnt[N], idx = 0;
void insert(char *str) { int p = 0; for (int i = 0; str[i]; ++i) { int e = str[i] - 'a'; if (!son[p][e]) son[p][e] = ++idx; p = son[p][e]; } ++cnt[p];}
int search(char *str) { int p = 0; for (int i = 0; str[i]; ++i) { int e = str[i] - 'a'; if (!son[p][e]) return 0; p = son[p][e]; }
return cnt[p];}0-1Trie
class Trie {public: static const int MAXN = 1e4; int tr[MAXN][2]; int ptr = 0;
void insert(int x) { int p = 0; for (int i = 31; i >= 0; --i) { bool c = x >> i & 1; if (!tr[p][c]) tr[p][c] = ++ptr; p = tr[p][c]; } }
int find(int x) { int p = 0, ans = 0; for (int i = 31; i >= 0; --i) { bool c = x >> i & 1; bool b = !c; if (tr[p][b]) { ans |= 1 << i; p = tr[p][b]; } else { p = tr[p][c]; } } return ans; }};并查集
普通并查集
class UnionFind {public: vector<int> f; vector<int> sz; int cc;
UnionFind(int n) : f(n), sz(n), cc(n) { iota(f.begin(), f.end(), 0); }
int find(int x) { if (f[x] != x) f[x] = find(f[x]); return f[x]; }
bool merge(int from, int to) { int x = find(from), y = find(to); if (x == y) return 0; f[x] = y; sz[y] += sz[x]; --cc; return 1; }};前缀和
二维前缀和
int s[N][N];
// 构建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];
// 查询int get(int x1, int y1, int x2, int y2) { return s[x2][y2] - s[x1-1][y2] - s[x2][y1-1] + s[x1-1][y1-1];}二维差分
int a[N][N], b[N][N];
// 区间加void add(int x1, int y1, int x2, int y2, int k) { b[x1][y1] += k; b[x2+1][y1] -= k; b[x1][y2+1] -= k; b[x2+1][y2+1] += k;}
// 还原for (int i = 1; i <= n; ++i) { for (int j = 1; j <= m; ++j) { a[i][j] = b[i][j] + a[i-1][j] + a[i][j-1] - a[i-1][j-1]; }}带权并查集
// 模板来源 https://leetcode.cn/circle/discuss/mOr1u6/// 根据题目用 UnionFind<int> uf(n) 或者 UnionFind<long long> uf(n) 初始化template<typename T>class UnionFind {public: vector<int> fa; // 代表元 vector<T> dis; // dis[x] 表示 x 到(x 所在集合的)代表元的距离
UnionFind(int n) : fa(n), dis(n) { // 一开始有 n 个集合 {0}, {1}, ..., {n-1} // 集合 i 的代表元是自己,自己到自己的距离是 0 ranges::iota(fa, 0); // iota(fa.begin(), fa.end(), 0); }
// 返回 x 所在集合的代表元 // 同时做路径压缩 int find(int x) { if (fa[x] != x) { int root = find(fa[x]); dis[x] += dis[fa[x]]; // 递归更新 x 到其代表元的距离 fa[x] = root; } return fa[x]; }
// 判断 x 和 y 是否在同一个集合(同普通并查集) bool same(int x, int y) { return find(x) == find(y); }
// 计算从 from 到 to 的相对距离 // 调用时需保证 from 和 to 在同一个集合中,否则返回值无意义 T get_relative_distance(int from, int to) { find(from); find(to); // to-from = (x-from) - (x-to) = dis[from] - dis[to] return dis[from] - dis[to]; }
// 合并 from 和 to,新增信息 to - from = value // 其中 to 和 from 表示未知量,下文的 x 和 y 也表示未知量 // 如果 from 和 to 不在同一个集合,返回 true,否则返回是否与已知信息矛盾 bool merge(int from, int to, T value) { int x = find(from), y = find(to); if (x == y) { // from 和 to 在同一个集合,不做合并 // to-from = (x-from) - (x-to) = dis[from] - dis[to] = value return dis[from] - dis[to] == value; } // x --------- y // / / // from ------- to // 已知 x-from = dis[from] 和 y-to = dis[to],现在合并 from 和 to,新增信息 to-from = value // 由于 y-from = (y-x) + (x-from) = (y-to) + (to-from) // 所以 y-x = (to-from) + (y-to) - (x-from) = value + dis[to] - dis[from] dis[x] = value + dis[to] - dis[from]; // 计算 x 到其代表元 y 的距离 fa[x] = y; return true; }};哈希
#include <bits/stdc++.h>using namespace std;using ull = unsigned long long;using ll = long long;
namespace hash_util {inline void hash_combine(ull& seed, ull value) { seed ^= value + 0x9e3779b9 + (seed << 6) + (seed >> 2);}
template <class T>inline void hash_combine(ull& seed, const T& value) { hash_combine(seed, hash<T>{}(value));}pair哈希
struct pair_hash { template <class T1, class T2> ull operator()(const pair<T1, T2>& p) const { ull seed = 0; hash_combine(seed, p.first); hash_combine(seed, p.second); return seed; }};tuple哈希
struct tuple_hash { template <class... Ts> ull operator()(const tuple<Ts...>& t) const { ull seed = 0; apply([&seed](const auto&... args) { (hash_combine(seed, args), ...); }, t); return seed; }};}树
LCA
#include <bits/stdc++.h>
using namespace std;using ll = long long;using pii = pair<int, int>;
class lca { vector<int> depth; vector<ll> dis; vector<vector<int>> pa;// 本质为步长为2^i的父亲数组
public: lca(const vector<vector<int>>& edges) { int n = edges.size() + 1; int m = bit_width((unsigned) n); vector<vector<pii>> g(n); depth.resize(n); dis.resize(n); pa.resize(n, vector<int>(m, -1));
for (auto& e : edges) { int x = e[0], y = e[1], w = e[2]; g[x].emplace_back(y, w); g[y].emplace_back(x, w); }
auto dfs = [&](auto&& dfs, int x, int fa) -> void { pa[x][0] = fa; for (auto& [y, w] : g[x]) { if (y != fa) { depth[y] = depth[x] + 1; dis[y] = dis[x] + w; pa[y][0] = x; dfs(dfs, y, x); } } }; dfs(dfs, 0, -1);
for (int i = 0; i < m - 1; ++i) { for (int x = 0; x < n; ++x) { int p = pa[x][i]; if (p != -1) { pa[x][i + 1] = pa[p][i]; } } } }
int get_kth_anc(int node, int k) { for (; k > 0 && node >= 0; k &= k - 1) { node = pa[node][countr_zero((unsigned) k)]; } return node; }
int get_lca(int x, int y) { if (depth[x] > depth[y]) swap(x, y);
y = get_kth_anc(y, depth[y] - depth[x]); if (y == x) return x;
for (int i = pa.size() - 1; i >= 0; --i) { int px = pa[x][i], py = pa[y][i]; if (px != py) { x = px; y = py; } } return pa[x][0]; }};串
KMP
vector<int> prefix_function(const string& s) { int n = s.size(); vector<int> pi(n); for (int i = 1; i < n; ++i) { int j = pi[i - 1]; while (j > 0 && s[i] != s[j]) j = pi[j - 1]; if (s[i] == s[j]) j++; pi[i] = j; } return pi;}
vector<int> kmp_match(const string& text, const string& pattern) { string s = pattern + "#" + text; vector<int> pi = prefix_function(s); vector<int> res;
int m = pattern.size(); for (int i = m + 1; i < s.size(); ++i) { if (pi[i] == m) res.push_back(i - 2 * m); } return res;}Z函数
vector<int> z_function(const string& s) { int n = s.size(); vector<int> z(n); int l = 0, r = 0;
for (int i = 1; i < n; ++i) { if (i <= r) z[i] = min(r - i + 1, z[i - l]);
while (i + z[i] < n && s[z[i]] == s[i + z[i]]) z[i]++;
if (i + z[i] - 1 > r) { l = i; r = i + z[i] - 1; } }
return z;}
vector<int> z_match(const string& text, const string& pattern) { string s = pattern + "#" + text; vector<int> z = z_function(s); vector<int> res;
int m = pattern.size(); for (int i = m + 1; i < s.size(); ++i) if (z[i] == m) res.push_back(i - m - 1);
return res;}AC自动机
struct AC { static const int N = 1000005; int tr[N][26], fail[N], cnt[N]; int idx = 0;
void insert(const string& s) { int p = 0; for (char c : s) { int u = c - 'a'; if (!tr[p][u]) tr[p][u] = ++idx; p = tr[p][u]; } cnt[p]++; }
void build() { queue<int> q; for (int i = 0; i < 26; ++i) if (tr[0][i]) q.push(tr[0][i]);
while (!q.empty()) { int u = q.front(); q.pop(); for (int i = 0; i < 26; ++i) { int v = tr[u][i]; if (v) { fail[v] = tr[fail[u]][i]; q.push(v); } else { tr[u][i] = tr[fail[u]][i]; } } } }
int query(const string& text) { int p = 0, res = 0; for (char c : text) { int u = c - 'a'; p = tr[p][u];
for (int j = p; j && cnt[j] != -1; j = fail[j]) { res += cnt[j]; cnt[j] = -1; // 防止重复计数 } } return res; }};SAM
struct SAM { static const int N = 200005;
struct State { int next[26]; int link, len; } st[N * 2];
int last, sz;
void init() { st[0].len = 0; st[0].link = -1; sz = 1; last = 0; }
void extend(char ch) { int c = ch - 'a'; int cur = sz++; st[cur].len = st[last].len + 1;
int p = last; while (p != -1 && !st[p].next[c]) { st[p].next[c] = cur; p = st[p].link; }
if (p == -1) { st[cur].link = 0; } else { int q = st[p].next[c]; if (st[p].len + 1 == st[q].len) { st[cur].link = q; } else { int clone = sz++; st[clone] = st[q]; st[clone].len = st[p].len + 1;
while (p != -1 && st[p].next[c] == q) { st[p].next[c] = clone; p = st[p].link; }
st[q].link = st[cur].link = clone; } }
last = cur; }
// 不同子串数量 long long count_distinct() { long long res = 0; for (int i = 1; i < sz; ++i) res += st[i].len - st[st[i].link].len; return res; }};文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!