专栏文章
P2014 [CTSC1997] 选课
P2014题解参与者 3已保存评论 2
文章操作
快速查看文章及其快照的属性,并进行相关操作。
- 当前评论
- 2 条
- 当前快照
- 1 份
- 快照标识符
- @minuxa97
- 此快照首次捕获于
- 2025/12/02 08:45 3 个月前
- 此快照最后确认于
- 2025/12/02 08:45 3 个月前
本文证明树上背包的时间复杂度是 的。换言之,上题可以加强至:,。
首先给出代码实现:
CPP#include <bits/stdc++.h>
using namespace std;
#define dbg(...) cerr << "[" << #__VA_ARGS__ << "] = ", debug_out(__VA_ARGS__)
template <typename T> void debug_out(T t) { cerr << t << endl; }
template <typename T, typename... Ts> void debug_out(T t, Ts... ts) {
cerr << t << ", ";
debug_out(ts...);
}
template <class F> struct y_combinator {
F f;
template <class... Args> decltype(auto) operator()(Args &&...args) {
return f(*this, std::forward<Args>(args)...);
}
};
template <class F> auto make_y_combinator(F f) { return y_combinator<F>{f}; }
int main() {
cin.tie(0)->sync_with_stdio(0);
int n, m;
cin >> n >> m;
vector<int> a(n + 1);
vector<vector<int>> e(n + 1);
for (int i = 1, f; i <= n; i++) {
cin >> f >> a[i];
e[f].emplace_back(i);
}
++m;
vector<vector<int>> dp(n + 1, vector<int>(m + 1));
vector<int> sz(n + 1);
auto dfs = make_y_combinator([&](auto self, int u) -> void {
dp[u][1] = a[u];
sz[u] = 1;
for (int v : e[u]) {
self(v);
for (int i = min(sz[u], m); i >= 1; i--) {
for (int j = min(sz[v], m - i); j >= 1; j--) {
dp[u][i + j] = max(dp[u][i + j], dp[u][i] + dp[v][j]);
}
}
sz[u] += sz[v];
}
});
dfs(0);
cout << dp[0][m] << endl;
return 0;
}
让我们重点关注:
CPPfor (int i = min(sz[u], m); i >= 1; i--) {
for (int j = min(sz[v], m - i); j >= 1; j--) {
dp[u][i + j] = max(dp[u][i + j], dp[u][i] + dp[v][j]);
}
}
这也就是说,对于每条边 ,该部分转移的时间复杂度为
其中:
- 表示 左边的子树大小之和,即 的子节点中在 之前被访问到的子节点的子树大小之和。
- 表示 的子树大小。
那么总时间复杂度即为
接下来存在两个 自然的观察:
-
考虑 ,由于边数 ,因此总时间复杂度不超过 。
-
考虑 ,这可以理解成在 处对所有满足如下条件的 进行计数:
- 在 左边的子树 中
- 在 的子树 中
注意到任意 仅会在它们的 处被计数一次,于是有 ,因此总时间复杂度不超过 。
然未尽其析。
下证时间复杂度为 。
我们将子树大小 的点称之为蓝点,子树大小 的点称之为红点。
同时把边分为三类:
- 蓝边:连接两个蓝点的边
- 黄边:连接红点和蓝点的边
- 红边:连接两个红点的边
例如 , 时,考虑下图:

接下来我们分四类讨论:
考虑所有蓝边,我们可以得到若干 极大蓝子树(图中有蓝点 6, 7, 9, 10, 11, 18, 19, 20 的子树)。根据上述 自然的观察 2,大小为 的子树内部对时间复杂度的贡献不超过 。假设这些 极大蓝子树 的大小为 ,则:
- 。
- 由于 极大蓝子树 是互斥的,有 。
这可以推出
于是所有蓝边对时间复杂度的贡献不超过 。
考虑所有黄边 ,此时所有 的子树即为 极大蓝子树,它们是互斥的,有 ,因此
于是所有黄边对时间复杂度的贡献不超过 。
注意到仅红点和红边也构成一棵树,我们称之为红树。
考虑 红树的叶子节点,其个数为 ,这是因为在原树中这些点的子树大小均 ,且这些子树互斥。
仅考虑满足如下条件的红边 (让我们称之为 深红边):
- 有至少两个红子节点(让我们称之为 深红点)。
(图中有 深红边 ,,,,深红点 )
可以将每个 深红点 理解为,对至少两个 红树的叶子节点 进行合并。因此 深红点 的个数 红树的叶子节点 的个数,从而 深红边 的数量也为 。
根据上述 自然的观察 1,每条边对时间复杂度的贡献不超过 ,因此所有 深红边 对时间复杂度的贡献不超过 。
最后考虑 浅红边 (即不是 深红边 的红边,图中有 ,),此时所有 左边的子树 是互斥的,有 ,因此
于是所有 浅红边 对时间复杂度的贡献不超过 。
综上,树上背包时间复杂度不超过 ,这显然没法更低了,因此就是 。
然犹未尽析。
有没有更直观一点的解释呢?
回到式子:
考虑 dfs 序 ,仿照 自然的观察 2,这可以理解成在 处对所有满足如下条件的 进行计数:
- 在 左边的子树 中,且
- 在 的子树 中,且
注意到任意 仅会在它们的 处被最多计数一次,且只有当 才会被计数。这样的 有 对,因此总时间复杂度为 。
相关推荐
评论
共 2 条评论,欢迎与作者交流。
正在加载评论...