20230419 训练记录:dp
Deque
Alice 和 Bob 玩游戏,轮流从 deque 的头/尾取走一个元素,Alice 拿走的数和为 \(X\),Bob 拿走的数和为 \(Y\)。Alice 想最大化 \(X - Y\) 而 Bob 想最小化 \(X - Y\)。问双方都采取最优策略情况下的 \(X - Y\) 为多少。
\(n \leq 3000; a_i \leq 10^9\)
用 \(f_{l, r, 0 / 1}\) 表示 Bob / Alice 先手情况下,在区间 \([l, r]\) 的最佳答案,所求即 \(f_{0, n - 1, 1}\)。有:
展开代码
#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<int> a(n);
for (int &i : a) std::cin >> i;
const ll inf = 1e18;
std::vector f(n, std::vector(n, std::array<ll, 2>{inf, inf}));
std::cout << [&, dp{[&](auto &&self, int l, int r, bool alice) -> ll {
if (f[l][r][alice] != inf) return f[l][r][alice];
ll ans = 0;
if (l == r) ans = a[l] * (alice ? 1 : -1);
else if (alice) ans = std::max(self(self, l + 1, r, 0) + a[l], self(self, l, r - 1, 0) + a[r]);
else ans = std::min(self(self, l + 1, r, 1) - a[l], self(self, l, r - 1, 1) - a[r]);
return f[l][r][alice] = ans;
}}]{
return dp(dp, 0, n - 1, 1);
}();
return 0;
}
Candies
求 \(k\) 拆成 \(n\) 个整数的方案数(整数划分数),但是 \(0 \leq x_i \leq a_i\)。
\(n \leq 100; k \leq 10^5\)。
整数划分数
整数划分数其实就是完全背包,有:
f[0] = 1; for (int i = 1; i <= n; i++) { for (int j = i; j <= n; j++) { f[j] += f[j - i]; } }
带上限制,用 \(f_{i, j}\) 表示使用前 \(i\) 个满足题意的数组成的和为 \(j\) 的方案数,所求即 \(f_{n, k}\)。有:
右侧为一段区间和,考虑使用前缀和优化。
展开代码
#include <bits/stdc++.h>
using ll = long long;
int main() {
std::cin.tie(nullptr)->sync_with_stdio(false);
int n, k;
std::cin >> n >> k;
std::vector<int> a(n);
for (int &i : a) std::cin >> i;
std::vector f(n + 1, std::vector(k + 1, 0LL)), s{f};
const int mod = 1000000007;
f[0][0] = s[0][0] = 1;
for (int i = 1; i <= k; i++) {
s[0][i] = (s[0][i - 1] + f[0][i]) % mod;
}
for (int i = 1; i <= n; i++) {
f[i][0] = s[i][0] = 1;
for (int j = 1; j <= k; j++) {
if (int ai = a[i - 1]; j <= ai) {
f[i][j] = s[i - 1][j];
} else {
f[i][j] = ((s[i - 1][j] - s[i - 1][(j - ai) - 1]) % mod + mod) % mod;
}
s[i][j] = (s[i][j - 1] + f[i][j]) % mod;
}
}
std::cout << f[n][k] << '\n';
return 0;
}
Slimes
同合并石子,区间 dp 模板题。满足四边形不等式。
展开代码
#include <bits/stdc++.h>
int read() {
int x = 0, f = 1, c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -1;
for (; isdigit(c); x = x * 10 + c - '0', c = getchar());
return x * f;
}
const int N = 410;
using ll = long long;
const ll inf = 1E18;
int n, a[N], s[N][N];
ll f[N][N], sum[N];
#define w(i, j) sum[j] - sum[i - 1]
int main() {
n = read();
for (int i = 1; i <= n; i++) {
a[i] = read();
}
for (int i = 1; i <= n; i++) {
sum[i] = sum[i - 1] + a[i];
s[i][i] = i;
}
for (int l = 2; l <= n; l++) {
for (int i = 1, j = l; j <= n; i++, j++) {
f[i][j] = inf;
for (int k = s[i][j - 1]; k <= s[i + 1][j]; k++) {
if (f[i][j] > f[i][k] + f[k + 1][j] + w(i, j)) {
f[i][j] = f[i][k] + f[k + 1][j] + w(i, j);
s[i][j] = k;
}
}
}
}
printf("%lld\n", f[1][n]);
return 0;
}
Matching
\(n\) 男 \(n\) 女,不同性别之间配对,如果 \(a_{i, j} = 1\) 表示 \(i\) 对 \(j\) 合适。只能一男一女,问多少合法方案数。
\(n \leq 21\)。
\(n\) 很小,考虑枚举子集。\(f_{i, s}\) 表示 \([1, i]\) 编号的男女,配对情况为 \(s\) 的方案数。则有:
展开代码
#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 a(n, std::vector(n, 0));
for (auto &i : a) for (auto &j : i) std::cin >> j;
std::vector f(n + 1, std::vector(1 << n, 0LL));
f[0][0] = 1;
const int mod = 1000000007;
for (int s = 0; s < 1 << n; s++) {
for (int i = 0; i < n; i++) if (f[i][s]) {
for (int j = 0; j < n; j++) if ((~s >> j & 1) && a[i][j]) {
(f[i + 1][s | (1 << j)] += f[i][s]) %= mod;
}
}
}
std::cout << f.back().back() << '\n';
return 0;
}
Indepedent Set
将树的所有节点染成黑白两色,求相邻节点不同时为黑的方案数。
\(n \leq 10^5\)。
乘法原理,一路乘上去,如果当前节点是白色就两种加起来,否则为黑色就乘以儿子涂成白色的方案数。
展开代码
#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(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);
}
const int mod = 1000000007;
std::vector f(n, std::array<int, 2>{});
[&, dfs{[&](auto &&self, int u, int p) -> void {
f[u][0] = f[u][1] = 1;
for (auto v : g[u]) if (v != p) {
self(self, v, u);
f[u][0] = (ll) f[u][0] * ((ll) f[v][0] + f[v][1]) % mod;
f[u][1] = (ll) f[u][1] * f[v][0] % mod;
}
}}]{
dfs(dfs, 0, -1);
}();
std::cout << ((ll)f[0][0] + f[0][1]) % mod << '\n';
return 0;
}
Flowers
给定 \(\{h\}_n, \{a\}_n\),求最长子序列 \(\{p\}_k\) 使得 \(h_{p_i}\) 上升的最大的 \(\sum \limits_{i = 1} ^ k a_{p_i}\)。
\(h_i \leq n \leq 2 \times 10^5; a_i \leq 10^9\)。
用 \(f_{i}\) 表示考虑到第 \(i\) 个元素结尾的最长上升子序列对应的最大和,则:
查询前缀最大值,此事树状数组能胜任。注意到 \(h_i\) 很小,可以用下标表示 \(h_i\),值表示 dp 的值。
展开代码
#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 h(n, 0), a(n, 0);
std::vector p(n + 1, 0LL);
for (auto &i : h) std::cin >> i;
for (auto &i : a) std::cin >> i;
ll f = 0, ans = 0;
for (int i = 0; i < n; i++) {
ll res = 0;
for (int x = h[i] - 1; x; x -= x & -x)
res = std::max(ans, p[x]);
f = res + a[i];
for (int x = h[i]; x <= n; x += x & -x)
p[x] = std::max(p[x], f);
ans = std::max(ans, f);
}
std::cout << ans << '\n';
return 0;
}
Walk
求长度为 \(k\) 的路径条数。
邻接矩阵 \(n \leq 50; k \leq 10^{18}\)。
熟知 Floyd 算法的 \(f_{i, j}\) 循环 \(k\) 次表示的是走 \(k\) 步的最短路径(这也是矩阵乘法的本质之一)。又因为 \(f_k = f_1^k\) 所以这是一道矩乘快速幂模板题。
展开代码
#include <bits/stdc++.h>
using ll = long long;
const int N = 50;
const int mod = 1000000007;
using matrix = std::array<std::array<int, N>, N>;
int n;
matrix operator *(const matrix &lhs, const matrix &rhs) {
matrix res{};
for (int i = 0; i < n; i++) {
for (int k = 0; k < n; k++) {
for (int j = 0; j < n; j++) {
res[i][j] = ((ll) res[i][j] + (ll) lhs[i][k] * rhs[k][j] % mod) % mod;
}
}
}
return res;
}
matrix power(matrix x, ll k) {
matrix res{};
for (int i = 0; i < n; i++) res[i][i] = 1;
for (; k; k /= 2, x = x * x)
if (k & 1) res = res * x;
return res;
}
int main() {
std::cin.tie(nullptr)->sync_with_stdio(false);
ll k;
std::cin >> n >> k;
matrix x{};
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
std::cin >> x[i][j];
}
}
matrix ans = power(x, k);
int res{};
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
res = ((ll) res + ans[i][j]) % mod;
}
}
std::cout << res << '\n';
return 0;
}
🙁 今日放下狠话,天梯赛打崩了就放弃这个我坚持了两年多的算法竞赛。说句心里话还是不太舍得,我幻想过太多次能够在大学里有一个支撑我走下去的理由,算法竞赛真是太合适了,可惜我削平了脚也穿不上这水晶鞋。我感到悲伤,但也十分释怀,一直以来我都只在乎此事,连恋爱、学业和工作都没有时间好好打理和准备...... 不过要是能坚持下去也会是一段十分难得的回忆呢!现阶段还是比较希望自己能够全力以赴吧!