20230420 训练记录:dp
Digit Sum
\(1 - k\) 中有多少数是 \(d\) 的倍数。
\(k \leq 10^{10\,000}; d \leq 100\)。
数位 dp,\(dp_{i, s, f}\) 表示 \(s \equiv x_{i - 1}x_{i - 2}\cdots x_2x_1x_0 \pmod d\),\(f = [x_{i - 1}x_{i - 2}\cdots x_2x_1x_0 \gt k_{i - 1}k_{i - 2}\cdots k_2k_1k_0]\)。则有(枚举新的位为 \(d'\)):
\(f' = [d' > k[i] \lor (d' = k[i] \land f = 1)]\)。
展开代码
#include <bits/stdc++.h>
using ll = long long;
int main() {
std::cin.tie(nullptr)->sync_with_stdio(false);
std::string s;
int d;
std::cin >> s >> d;
int n = s.size();
std::vector k(n, 0);
for (int i = 0; i < n; i++) {
k[i] = s[n - i - 1] - '0';
}
const int mod = 1000000007;
std::vector f(n + 1, std::vector(d, std::array<int, 2>{}));
f[0][0][0] = 1;
for (int i = 0; i < n; i++) {
for (int s = 0; s < d; s++) {
for (int _f : {0, 1}) {
for (int _d = 0; _d < 10; _d++) {
int new_s = (s + _d) % d;
int new_f = _d > k[i] || (_d == k[i] && _f == 1);
f[i + 1][new_s][new_f] = ((ll) f[i + 1][new_s][new_f] + f[i][s][_f]) % mod;
}
}
}
}
std::cout << (f[n][0][0] - 1 + mod) % mod << '\n';
return 0;
}
Permutation
Grouping
将物品分组,如果 \(i, j\) 在一组将会得到 \(a_{i, j}\) 的分。问所有分组情况下最大得分。
\(n \leq 16; |a_{i, j}| \leq 10^9\)。
分成若干组包含一个子问题:分成两组。
状压,\(f_i\) 表示其中一组的分组情况为 \(i\) 的情况下的最大得分。则按照上面的划分,只需考虑其被分为 \(f_j, f_{i \backslash j}, \,(i \in j)\) 的方案。即:
💡 如何枚举非空子集
for (int j = i; j; --j &= i) { }
当然,本题需要非空、真子集,因此初始
int j = i & (i - 1)
(抹去最后一个 \(0\))。
复杂度为 \(\mathcal O(n^2 2^n + 3^n)\)。
依二项式定理,\(\displaystyle\sum\limits_{k = 0}^{n} {n \choose k} 2^k = \sum_{k=0}^n { n \choose k } (1)^{n-k} (2)^k = (1 + 2) ^ n\) [1]
sh %%%
展开代码
#include <bits/stdc++.h>
using ll = long long;
int main() {
std::cin.tie(nullptr)->sync_with_stdio(false);
int n;
std::cin >> n;
std::vector g(n, std::vector(n, 0));
for (auto &i : g) for (auto &j : i) std::cin >> j;
std::vector f(1 << n, -1LL);
f[0] = 0;
std::cout << [&, dp{[&](auto &&self, int i) -> ll {
if (~f[i]) return f[i];
f[i] = 0;
for (int j = 0; j < n; j++) if (i >> j & 1) {
for (int k = j + 1; k < n; k++) if (i >> k & 1) {
f[i] += g[j][k];
}
}
for (int j = i & (i - 1); j; --j &= i) {
f[i] = std::max(f[i], self(self, j) + self(self, i ^ j));
}
return f[i];
}}]{
return dp(dp, (1 << n) - 1);
}();
return 0;
}
Subtree
将树的节点染成黑白,问 \(i\; (1 \leq i \leq n)\) 在树上唯一黑色连通块的方案数。
\(n \leq 10^5\),不保证模数为素数。
容易处理出以 \(u\) 为根的答案 \(f_u = \prod\limits_{v \in u} (\underline{f_v}_{black} + \underline{1}_{white})\),考虑换根求其他点答案:
实际上 \(f_u\) 为 \(u\) 以下的答案,而另一部分当然是 \(u\) 以上的答案,即其兄弟的答案 \(\dfrac{f_p}{f_u + 1}\),其中 \(u \in p\)。然而不保证模数的素性就不一定有逆元,因此考虑维护 \(u\) 的兄弟节点 \(s\) 的 \(\{f_s + 1\}\) 前缀乘积、后缀乘积。将上下两部分乘起来即可。
展开代码
可惜没有 \(n = m = 1\) 的数据。
#include <bits/stdc++.h>
using ll = long long;
int main() {
std::cin.tie(nullptr)->sync_with_stdio(false);
int n, m;
std::cin >> n >> m;
if (n == 1) {
std::cout << 1 % m << '\n';
std::exit(0);
}
std::vector g(n, std::vector(0, 0));
for (int i = 1; i < n; i++) {
int u, v;
std::cin >> u >> v;
u -= 1, v -= 1;
g[u].push_back(v);
g[v].push_back(u);
}
std::vector f(n, 1);
std::vector prefix{g}, suffix{g};
[&, dfs{[&](auto &&self, int u, int p) -> void {
for (int v : g[u]) if (v != p) {
self(self, v, u);
f[u] = (ll) f[u] * (1 + f[v]) % m;
}
std::fill(prefix[u].begin(), prefix[u].end(), 1);
for (int i = 1; i < (int) g[u].size(); i++) {
prefix[u][i] = prefix[u][i - 1];
if (int v = g[u][i - 1]; v != p) {
prefix[u][i] = (ll) prefix[u][i] * (f[v] + 1) % m;
}
}
std::fill(suffix[u].begin(), suffix[u].end(), 1);
for (int i = (int) g[u].size() - 2; ~i; i--) {
suffix[u][i] = suffix[u][i + 1];
if (int v = g[u][i + 1]; v != p) {
suffix[u][i] = (ll) suffix[u][i] * (f[v] + 1) % m;
}
}
}}]{
dfs(dfs, 0, -1);
}();
std::vector h(n, 1);
[&, dfs{[&](auto &&self, int u, int p, int pid) -> void {
if (~p) {
h[u] = ((ll) h[u] + (ll) h[p] * prefix[p][pid] % m * suffix[p][pid] % m) % m;
}
for (int i = 0; i < (int) g[u].size(); i++) if (int v = g[u][i]; v != p) {
self(self, v, u, i);
}
}}]{
dfs(dfs, 0, -1, -1);
}();
for (int i = 0; i < n; i++) {
std::cout << (ll) f[i] * h[i] % m << '\n';
}
return 0;
}
Intervals
Tower
Grid 2
同 Grid 1,但给出所有障碍点的坐标。
障碍点数 \(n \leq 10^3; h, w \leq 10^5\)。
很容易计算出没有任何障碍点的答案,即从 \((1, 1)\) 到 \((n, m)\) 的答案实际上是二项式系数 \(\displaystyle{n - 1 + m - 1 \choose n - 1}\),实现上 \(0\)-indexed 更容易。
只需要将障碍部分都减去。不用担心减重了,由容斥原理可知许多项实际上被减去、抵消、减去、抵消…… 即用 \(f_i\) 表示考虑走过前 \(i\) 个障碍的方案数,为了方便可以再加一个障碍 \((h - 1, w - 1)\),这样 \(f_{n + 1}\) 即为所求。对于每个 dp 值,都减去其前面的障碍点贡献:
展开代码
#include <bits/stdc++.h>
using ll = long long;
int main() {
std::cin.tie(nullptr)->sync_with_stdio(false);
int h, w, n;
std::cin >> h >> w >> n;
std::vector p(n, std::pair{0, 0});
for (auto &[x, y] : p) {
std::cin >> x >> y;
x -= 1, y -= 1;
}
p.emplace_back(h - 1, w - 1);
const int N = 200001;
const int mod = 1000000007;
std::vector<int> fac(N, 0), inv(N, 0), ifc(N, 0);
fac[0] = fac[1] = inv[0] = inv[1] = ifc[0] = ifc[1] = 1;
for (int i = 2; i < N; i++) {
fac[i] = (ll) fac[i - 1] * i % mod;
inv[i] = ((ll) mod - mod / i) % mod * inv[mod % i] % mod;
ifc[i] = (ll) ifc[i - 1] * inv[i] % mod;
}
auto binom = [&](int n, int m) -> ll {
if (n < m || m < 0) return 0ll;
return (ll) fac[n] * ifc[m] % mod * ifc[n - m] % mod;
};
std::vector f(n + 1, 0);
std::sort(p.begin(), p.end());
for (int i = 0; i < n + 1; i++) {
auto [x, y] = p[i];
f[i] = binom(x + y, x);
for (int j = 0; j < i; j++) {
auto [nx, ny] = p[j];
if (x >= nx && y >= ny) {
f[i] = ((ll) f[i] - (ll) f[j] * binom(x - nx + y - ny, x - nx) % mod + mod) % mod;
}
}
}
std::cout << f[n] << '\n';
return 0;
}