P9669 [ICPC 2022 Jinan R] DFS Order 2
DFS Order 2
回滚背包.
思路
抛开其它不谈, 考虑节点 \(1\) 在第 \(1\) 个位置出现的数量如何计算. 我们令 \(h_u\) 表示以 \(u\) 为根的子树的 DFS 序的方案数, 可以发现我们有
前面的 \(|son_u|!\) 是因为对于所有儿子我们可以进行全排列.
我们记 \(ans_{i, j}\) 为只看节点 \(i\), 不管它子树顺序, 亦即将它的子树缩成一个点, 节点 \(i\) 在 DFS 序列中第 \(j\) 个出现的方案数. 那么最终输出的就是 \(ans_{i, j} \times h_i\).
根据 DFS 序的性质, 我们自上而下的进行转移是更加自然的, 接下来考虑如何从节点 \(u\) 转移到节点 \(v \in son_u\).
可以发现, 如果要进行转移, 我们有几个必须枚举的变量
-
节点 \(u\) 在 DFS 序列中出现的位置 \(i\).
-
有多少个 \(v\) 的兄弟子树在 \(v\) 子树之前被遍历了, 同时也需要枚举他们的大小.
-
对于这个问题, 我们不难想到使用 \(01\) 背包进行解决. 令 \(sz_i\) 表示以 \(i\) 为根的子树大小, \(f_{i, j}\) 表示有 \(i\) 个兄弟子树排在了 \(v\) 之前, 大小总和为 \(j\) 的方案数.
那么有十分套路的转移式子
\[f_{i, j} \gets f_{i - 1, j - sz_v} \]最后不要忘记除了 \(v\) 的其它子树内部还有独立的方案数 \(\displaystyle \frac{h_u}{|son_u|! \times h_v} \times i! \times (|son_u| - i - 1)!\).
-
需要注意的是, 如果我们对于每一个儿子 \(v\), 都做一遍背包, 那总时间复杂度是 \(\mathcal{O}(n^4)\), 无法通过. 需要使用回退背包了将复杂度优化至 \(\mathcal{O}(n^3)\).
为了简化式子, 我们设 \(\displaystyle t_j = g_{i, j} \times \frac{h_u}{|son_u|! \times h_v} \times i! \times (|son_u| - i - 1)!\), 其中 \(g_{i, j}\) 表示不考虑 \(v\) 子树的背包. 那么对于 \(ans\) 数组的转移就有 \(\displaystyle ans_{v, i + j + 1} \gets ans_{u, i} \times t_j\).
什么是回滚背包? 总的来说, 这指的是一类问题
-
当前有 \(n\) 个物品, 少了第 \(i\) 个物品后装满大小为 \(j\) 的背包的方案数.
其中 \(n, m \le 2 \times 10^3\).
暴力做法是将每个物品剔除, 做 \(n\) 次背包, 时间复杂度 \(\mathcal{O}(n^2m)\).
观察 \(01\) 背包的转移
for (int j = m; j >= w[i]; --j) {
f[j] += f[j - w[i]];
}
由于少了 \(i\) 物品, 所以我们需要少一次这样的转移
memcpy(g, f, sizeof f);
for (int j = w[i]; j <= m; ++j) {
g[j] -= g[j - w[i]];
}
在这道题里面是一样的, 就不过多赘述了.

浙公网安备 33010602011771号