structBarrett { i64 d; i128 b; explicitBarrett(i64 d) : d(d), b(((i128) 1 << 64) / d) { } friend i64 operator %(i64 a, const Barrett &m) { i64 w = (m.b * a) >> 64; w = a - w * m.d; return w >= m.d ? w - m.d : w; } friend i64 operator /(i64 a, const Barrett &d) { i64 w = (d.b * a) >> 64; return a - w * d.d >= d.d ? w + 1 : w; } };
基姆拉尔森计算公式
以 1970 年 1 月 1 日为星期四为基准,累加日期计算 y 年 m 月 d
日是星期几。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
constint ds[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; boolis_leap(int y){ return y % 400 == 0 || (y % 4 == 0 && y % 100 != 0); } intget_days(int y, int m){ return ds[m - 1] + (is_leap(y) && m == 2); } intget_week(int y, int m, int d){ int res = 2; for (int i = 1970; i < y; i++) res += 365 + is_leap(i); for (int i = 1; i < m; i++) res += get_days(y, i); res += d; return res % 7 + 1; }
蔡勒公式
基准相同,可以快速计算任意年份,返回 0 代表星期日。
1 2 3 4 5
intget_week(int y, int m, int d) { if (m == 1 || m == 2) m += 12, y--; return (d + 2 * m + 3 * (m + 1) / 5 + y + y / 4 - y / 100 + y / 400 + 1) % 7; }
三分算法
整数范围凹函数三分算法,实现了对函数平台区域的处理,若平均平台长度为
k ,则期望复杂度约为 O(klog n) 。
1 2 3 4 5 6 7 8 9 10
i64 l = 0, r = 1e18, al, ar; while (l < r) { i64 ml = (l + r) / 2, mr = ml + 1; al = f(ml), ar = f(mr); while (al == ar && ml > l) al = f(--ml);
if (al >= ar) l = mr; else r = ml; } cout << min(al, ar) << endl;
排序算法
排序算法是将数据排列的方法,在基础排序算法的基础上上可以扩展出更多功能。
快速排序
基于随机选取基数的三路快速排序,是不稳定排序,可以在此基础上修改出线性第
k 大算法。
1 2 3 4 5 6 7 8 9 10 11 12
auto qsort = [&](auto &&self, int l, int r) { if (r - l <= 1) return; auto pivot = a[l + rnd() % (r - l)]; int i = l, j = l, k = r; while (i < k) { if (a[i] < pivot) swap(a[i++], a[j++]); elseif (a[i] > pivot) swap(a[i], a[--k]); else i++; } self(self, l, j); self(self, k, r); };
归并排序
基于分治思想将数组分段排序后合并,是稳定排序,可以在此基础上修改用于解决偏序问题其中一维。
1 2 3 4 5 6 7 8 9 10 11 12 13
auto msort = [&](auto self, int l, int r) { if (r - l <= 1) return; int m = l + r >> 1; self(self, l, m), self(self, m, r); int i = l, j = m, k = l; while (i < m && j < r) { if (a[i] <= a[j]) b[k++] = a[i++]; else b[k++] = a[j++]; } while (i < m) b[k++] = a[i++]; while (j < r) b[k++] = a[j++]; for (i = l; i < r; ++i) a[i] = b[i]; };
搜索算法
搜索,也就是对状态空间进行枚举,通过穷尽所有的可能来找到最优解,或者统计合法解的个数。
Dancing links X 算法
精确覆盖问题要求在给定集合中选取若干互不相交的子集,使得目标集合的每个元素恰好被覆盖一次。
Dancing Links X 算法通过高效回溯和双向链表优化搜索,常用于解决数独、
N 皇后、 Pentomino 拼图等组合优化问题。
structDLX { structnode { int l = 0, r = 0, u = 0, d = 0, row = -1, col = -1; };
vector<node> adj; vector<int> head, sz, ans;
explicitDLX(int r, int c) : adj(c + 1), head(r + 1, -1), sz(c + 1) { for (int i = 0; i <= c; ++i) { adj[i].l = i - 1, adj[i].r = i + 1; adj[i].u = adj[i].d = adj[i].col = i; } adj[0].l = c, adj[c].r = 0; }
voidinsert(int r, int c){ int id = (int) adj.size(); adj.emplace_back(0, 0, adj[c].u, c, r, c); adj[c].u = adj[adj[c].u].d = id; if (head[r] == -1) { head[r] = adj[id].l = adj[id].r = id; } else { int first = head[r]; int last = adj[first].l; adj[id].l = last, adj[id].r = first; adj[last].r = adj[first].l = id; } sz[c]++; }
voidremove(int c){ adj[adj[c].r].l = adj[c].l; adj[adj[c].l].r = adj[c].r; for (int i = adj[c].d; i != c; i = adj[i].d) { for (int j = adj[i].r; j != i; j = adj[j].r) { adj[adj[j].d].u = adj[j].u; adj[adj[j].u].d = adj[j].d; sz[adj[j].col]--; } } }
voidrestore(int c){ for (int i = adj[c].u; i != c; i = adj[i].u) { for (int j = adj[i].l; j != i; j = adj[j].l) { adj[adj[j].d].u = adj[adj[j].u].d = j; sz[adj[j].col]++; } } adj[adj[c].r].l = adj[adj[c].l].r = c; }
booldance(){ if (adj[0].r == 0) returntrue; int c = adj[0].r; for (int i = adj[c].r; i != 0; i = adj[i].r) { if (sz[i] < sz[c]) c = i; } remove(c); for (int i = adj[c].d; i != c; i = adj[i].d) { ans.push_back(adj[i].row); for (int j = adj[i].r; j != i; j = adj[j].r) remove(adj[j].col); if (dance()) returntrue; for (int j = adj[i].l; j != i; j = adj[j].l) restore(adj[j].col); ans.pop_back(); } restore(c); returnfalse; } };
array<array<int, 9>, 9> ma{}; auto get = [&](int x, int y, int k)-> array<int, 4> { int box = x / 3 * 3 + y / 3; return { x * 9 + y + 1, 81 + x * 9 + k + 1, 162 + y * 9 + k + 1, 243 + box * 9 + k + 1 }; };
int id = 0; DLX solver(730, 324); vector<tuple<int, int, int> > rows; for (int x = 0; x < 9; ++x) { for (int y = 0; y < 9; ++y) { if (ma[x][y] != 0) { int k = ma[x][y] - 1; for (int c: get(x, y, k)) solver.insert(id, c); rows.emplace_back(x, y, k); id++; } else { for (int k = 0; k < 9; ++k) { for (int c: get(x, y, k)) solver.insert(id, c); rows.emplace_back(x, y, k); id++; } } } }
solver.dance(); for (int rid: solver.ans) { auto [x, y, k] = rows[rid]; ma[x][y] = k + 1; }
动态规划算法
具有最优子结构,无后效性和子问题重叠的问题可以使用动态规划解决。
完全背包 DP
定义价值重量比为价值与重量的比值,则第 i 种物品的价值重量比为 $\frac{v_i}{w_i}$ 。
设第 s
种物品为价值重量比最大的那些物品之中重量最小的那种物品。
设使承重量为 W
的背包所能获得最大价值的方案中,有 a 件非第 s 种物品,有 b 件第 s 种物品。
于是转化为有 x 件非第 s 种物品, y 件第 s 种物品,且在这 x 件非第 s
种物品中,不存在若干件物品的重量和为 ws 的倍数。
由鸽笼原理得:x < ws ,故这 x 件物品的重量和最大为 (ws − 1)max(wi), i ≠ s
,不超过 u2。
ans = max(dp[w] + ⌊(W − w) ÷ ws⌋ × vs), 1 ≤ w ≤ u2
执行这样的 dp ,可以在 O(u3)
的时间复杂度解决完全背包问题,其中 u 为最大的物品重量。
以下代码提供了简洁的实现,并使用数论方法将时间复杂度优化到 O(u2log u)
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
vector<int> id(n); iota(id.begin(), id.end(), 0); ranges::sort(id, [&](int x, int y) { return (i64) v[x] * w[y] > (i64) v[y] * w[x]; }); vector<i64> dp(min(W, u * u) + 1); for (int i = 1; i <= min(W, u * u); ++i) { for (int j = 0; j < min((3 * u * u + i - 1) / i, n); ++j) { if (w[id[j]] <= i) dp[i] = max(dp[i], dp[i - w[id[j]]] + v[id[j]]); } } i64 m = (max(0, W - u * u) + w[id[0]] - 1) / w[id[0]]; i64 ans = dp[W - m * w[id[0]]] + m * v[id[0]];
混合背包 DP
将背包按对 w[i]
取模的余数分组,转移时同一组价值差满足等差数列,且每组之间的状态转移不互相影响,因此使
val = dp[k * w[i] + j] − k * v[i]
,每个容量的背包从 val 最大值转移,且
d[i
固定,可以使用单调队列优化,时间复杂度 O(nW) 。
下列代码中 d
为每个物品可被选择的次数,设为 inf
以选择无穷次。
1 2 3 4 5 6 7 8 9 10 11 12 13
vector<int> dp(W + 1); for (int i = 0; i < n; i++) { for (int j = 0; j < w[i]; j++) { deque<info> deq; for (int k = 0; k * w[i] + j <= W; k++) { int val = dp[k * w[i] + j] - k * v[i]; while (!deq.empty() && val > deq.back().val) deq.pop_back(); deq.emplace_back(k, val); if (deq.front().k < k - d[i]) deq.pop_front(); dp[k * w[i] + j] = deq.front().val + k * v[i]; } } }
状压 DP
状压 DP 通过将状态压缩为整数来达到优化转移的目的。
旅行商问题
旅行商问题求经过每个点的一次或多次的最短回路或通路,是组合优化中的一个
NPH 问题。
dp[i][j]
表示经过状态 i
所表示的点,到达 j
点的最短通路,经过恰当的初始化和后处理可以解决各类旅行商问题。
1 2 3 4 5 6 7 8 9 10 11 12 13
array<int, 20> val; val.fill(inf); vector<array<int, 20> > dp(1 << n, val); for (int i = 0; i < n; ++i) dp[1 << i][i] = 0; for (int i = 0; i < 1 << n; ++i) { for (int j = 0; j < n; ++j) { if (dp[i][j] == inf) continue; for (int k = 0; k < n; ++k) { if (i & 1 << k) continue; dp[i + (1 << k)][k] = min(dp[i + (1 << k)][k], dp[i][j] + maxd[j][k]); } } }
轮廓线 DP
给定一个 n × m 的矩形网格,计算用
1 × 2
多米诺骨牌完全平铺该网格的方法数。
dp[i][j][s]
表示第 i 行第 j 列的轮廓线状态为 s
时的方案数,逐格进行
DP,但记录的状态是各行内的轮廓线上格子的覆盖情况。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
if (n < m) swap(n, m); vector<i64> dp(1 << m); dp[(1 << m) - 1] = 1; for (int i = 1; i <= n; i++) { for (int j = 0; j < m; j++) { vector<i64> ndp(1 << m); for (int k = 0; k < 1 << m; k++) { if (k & 1 << j) ndp[k ^ 1 << j] += dp[k]; if (j && !(k & 1 << j - 1) && (k & 1 << j)) ndp[k | 1 << j - 1] += dp[k]; if (i && !(k & 1 << j)) ndp[k | 1 << j] += dp[k]; } dp = std::move(ndp); } } cout << dp[(1 << m) - 1] << '\n';
auto get = [&](int x, int k) { vector<int> a{0}; while (x) a.push_back(x % 10), x /= 10; vector mem(a.size(), vector<int>(a.size(), -1)); auto dfs = [&](auto &&self, int pos, bool limit, bool lead0, int sum) -> int { if (!pos) return sum; if (!limit && !lead0 && ~mem[pos][sum]) return mem[pos][sum]; int res = 0; int up = limit ? a[pos] : 9; for (int i = 0; i <= up; i++) { int c0 = lead0 && i == 0; res += self(self, pos - 1, limit && i == up, c0, c0 ? 0 : sum + (i == k)); } if (!limit && !lead0) mem[pos][sum] = res; return res; }; returndfs(dfs, a.size() - 1, true, true, 0); };
int s = 0, c = 0; for (int f = 0; f < n; c += a[f++].c) { if (a[s].x == a[f].x && a[s].y == a[f].y && a[s].z == a[f].z) continue; a[s].c = c, a[s].a = c - 1; a[++s] = a[f], c = 0; } a[s].c = c, a[s].a = c - 1; a.resize(++s);
Fenwick<int> bit(k + 1); auto cdq = [&](auto &&self, int l, int r) -> void { if (r - l <= 1) return; int m = l + r >> 1; self(self, l, m), self(self, m, r); int i = l, j = m; while (j < r) { while (i < m && a[i].y <= a[j].y) bit.add(a[i].z, a[i].c), i++; a[j].a += bit.sum(a[j].z), j++; } for (j = l; j < i; j++) bit.add(a[j].z, -a[j].c); inplace_merge(a.begin() + l, a.begin() + m, a.begin() + r, cmpy); }; cdq(cdq, 0, s);
constint len = (int) pow(n, 0.67); ranges::sort(qs, [&](auto &x, auto &y) { if (x.l / len != y.l / len) return x.l < y.l; if (x.r / len != y.r / len) return x.l / len & 1 ? x.r > y.r : x.r < y.r; return x.r / len & 1 ? x.t < y.t : x.t > y.t; });
auto movet = [&](int i, int val) { auto [x, u, v] = ms[i]; if (val == -1) swap(u, v); a[x] = v; if (x < l || x >= r) return; cnt[u]--, b[bk[u]]--; cnt[v]++, b[bk[v]]++; };
for (auto &q: qs) { while (l > q.l) movelr(--l, 1); while (r < q.r) movelr(r++, 1); while (l < q.l) movelr(l++, -1); while (r > q.r) movelr(--r, -1); while (t < q.t) movet(++t, 1); while (t > q.t) movet(t--, -1); q.ans = query(); }