10 动态规划
10.1 最长上升子序列 LIS
// 返回最长单调上升子序列, O(nlogn)
vector<int> LIS(vector<int>& arr) {
int len = arr.size() - 1; // 1-base
int ans = 0;
vector<int> low(2);
low[1] = arr[1], ans = 1;
for (int i = 2; i <= len; i++) {
if (arr[i] > low[ans]) {
low.push_back(arr[i]);
ans++;
} else {
int p = lower_bound(low.begin() + 1, low.end(), arr[i]) - low.begin();
low[p] = arr[i];
}
}
return move(low);
}
10.2 换根dp
void link(int x, int y) { // 子树y的贡献并入结点x
dp[x] += dp[y];
}
void cut(int x, int y) { // 结点x删除子树y的贡献
dp[x] -= dp[y];
}
void dfs1(int x, int fa, vector<vector<int>> &g) { // 统计子树对根的贡献
dp[x] = 1;
for (y : g[x]) {
if (y == fa) continue;
dfs1(y, x, g);
link(x, y);
}
}
void dfs2(int x, int fa, vector<vector<int>> &g) {
ans[x] = dp[x]; // 进入结点时,统计答案
for (int y : g[x]) {
if (y == fa) continue;
cut(x, y), link(y, x); // 换根
dfs2(y, x, g);
cut(y, x), link(x, y); // 回溯
}
}
10.3 SOSdp
\(O(n\cdot 2^n)\) 处理子集,超集的和。
void sum_over_subsets_dp() {
// 1. 初始化 DP 数组
// F[mask] 的初始值就是 A[mask] 本身,因为 mask 是自身的子集
for (int mask = 0; mask < (1 << N); ++mask) {
F[mask] = A[mask];
}
// 2. 按位进行 DP
// 外层循环遍历每一位 (维度)
for (int i = 0; i < N; ++i) {
// 内层循环遍历所有 mask
for (int mask = 0; mask < (1 << N); ++mask) {
// 如果 mask 的第 i 位是 1
// 那么 F[mask] 的值需要加上 F[mask ^ (1 << i)] 的贡献
// mask ^ (1 << i) 是 mask 去掉第 i 位的结果,是 mask 的一个子集
if (mask & (1 << i)) {
F[mask] += F[mask ^ (1 << i)];
}
}
}
}
void sum_over_supersets_dp() {
// 1. 初始化
for (int mask = 0; mask < (1 << N); ++mask) {
F[mask] = A[mask];
}
// 2. 按位进行 DP
for (int i = 0; i < N; ++i) {
// 内层循环 mask 需要从大到小
for (int mask = (1 << N) - 1; mask >= 0; --mask) {
// 如果 mask 的第 i 位是 0
// 那么 F[mask] 的值需要加上 F[mask | (1 << i)] 的贡献
// mask | (1 << i) 是 mask 加上第 i 位的结果,是 mask 的一个超集
if (!(mask & (1 << i))) {
F[mask] += F[mask | (1 << i)];
}
}
}
}