原题链接:F. Removing Leaves
题目大意:
给出一棵 n n n 个节点树, n − 1 n - 1 n−1 条边的树,每一个点 i i i 有一个权值 a i a_i ai ,再给出一个数字 k k k 。
现在你可以选出一些点构成一个点集,且对于点集中任意两点 x , y x,y x,y 的最短距离 d d d 来说都满足 d > k d > k d>k 。
现在要你回答在所有选出点集的方案中,选出点的点权之和最大是多少。
数据范围: ( 1 ≤ n , k ≤ 200 , 1 ≤ a i ≤ 1 0 5 ) (1\leq n,k \leq 200, 1 \leq a_i \leq 10^{5}) (1≤n,k≤200,1≤ai≤105)
解题思路:
这题看起来贪心没有什么入手点,考虑树形 D P DP DP 。
我们设计这样一个状态:
d p u , d dp_{u,d} dpu,d 代表 u u u 节点的子树中,选出一些节点 v v v 且所有选中节点中深度最小的节点深度至少为 d d d 的最大权值和。(注意是 > k >k >k 而不是 ≥ k \geq k ≥k,那么我们这里转化为 ≥ k + 1 \geq k+1 ≥k+1 方便理解)
那么还是和传统的树形 D P DP DP 的思考方式一样,我们把整棵树的问题转化到一棵棵子树中,从而再从子树中的答案转移到自己身上。
假设节点 u u u 深度为 0 0 0 ,我们枚举这个最小深度 d d d 进行转移:
- 如果
d
=
0
d=0
d=0,那么我们此时必选根节点,转移方程就为:
d p [ u ] [ 0 ] = a [ u ] + ∑ v ∈ s o n u d p [ v ] [ k ] \begin{array}{c} dp[u][0] =a[u]+\sum_{v \in son_u}dp[v][k] \end{array} dp[u][0]=a[u]+∑v∈sonudp[v][k]
这个转移方程很好理解,假设我们必须选 u u u 节点,那么我们就在从距离点 u u u 为 k + 1 k+1 k+1 的点的子树中选剩下能选的节点即可。
而我们是枚举点 u u u 的子节点 v v v 的,此时我们自带了 1 1 1 的距离,所以我们查询的是在 v v v 的子树中,深度最小为 k k k 的子节点的最大权值和。
- 如果 d ≠ 0 d \neq 0 d=0,那么我们此时会选择点 u u u 的子节点,即点 v v v 子树中的一些节点,在其中取一个最大值。
d p [ u ] [ d ] = max ( d p [ u ] [ d ] , d p [ v ] [ d − 1 ] + ∑ v ′ ∈ s o n u ∖ { v } d p [ v ′ ] [ max ( d e p − 1 , k − d e p ) ] ) \begin{array}{c} dp[u][d] = \max(dp[u][d],dp[v][d-1]+\sum_{v' \in son_u∖\{v\}}dp[v'][\max(dep-1,k-dep)]) \end{array} dp[u][d]=max(dp[u][d],dp[v][d−1]+∑v′∈sonu∖{v}dp[v′][max(dep−1,k−dep)])
看起来很复杂,解释一下转移方程:
最外层的 max \max max 就是在所有方案中取最大值,我们看内层的转移。
d p [ v ] [ d − 1 ] + ∑ v ′ ∈ s o n u ∖ { v } d p [ v ′ ] [ max ( d − 1 , k − d ) ] \begin{array}{c} dp[v][d-1]+\sum_{v' \in son_u∖\{v\}}dp[v'][\max(d-1,k-d)] \end{array} dp[v][d−1]+∑v′∈sonu∖{v}dp[v′][max(d−1,k−d)]
假设我们要在 u u u 的子树中取一个深度为 d d d 的节点,即我们要在其中一个子节点 v v v 的子树中取深度为 d − 1 d-1 d−1 的节点,然后再剩余子节点 v ′ v' v′ 的子树中取深度为 max ( d − 1 , k − d ) \max(d-1,k-d) max(d−1,k−d) 的节点,解释一下这个 max \max max 为什么这样写。
- 当 k − d > d − 1 k-d > d-1 k−d>d−1 时,我们只能在深度为 k − d k-d k−d 的子树中取点,才能使得我们的节点 v v v 和其他节点 v ′ v' v′ 的距离 d > k + 1 d > k + 1 d>k+1,这个很好理解。
- 当 k − d ≤ d − 1 k - d \leq d-1 k−d≤d−1 ,如果我们从 v ′ v' v′ 子树中取深度为 k − d k-d k−d 的节点,那么这个节点的深度加上 1 1 1 会比我们的 d d d 更小,虽然满足了 v , v ′ v,v' v,v′ 两点间距至少为 k k k ,但这和我们状态设计相冲突。我们要的是 深度最小的节点深度至少为 d d d ,因此此时我们只能在 v ′ v' v′ 的子树中取深度为 d − 1 d-1 d−1 的节点。
看起来很难懂,我们直接上图解释一下。
-
左图要使得选的点距离 d ≥ k + 1 d \geq k+1 d≥k+1 那么我们选了橙色点后,我们只能选黄色点来进行转移,即 d p [ u ] [ 1 ] = max { d p [ v ] [ 0 ] + d p [ v ′ ′ ] [ 1 ] + d p [ v ′ ] [ 1 ] } dp[u][1] = \max\{dp[v][0]+dp[v''][1]+dp[v'][1]\} dp[u][1]=max{dp[v][0]+dp[v′′][1]+dp[v′][1]} 。
-
右图我们选了橙色点后,在满足两点间距大于 k k k 的前提下,我们是可以选图上绿色的这两个点的,但注意我们的状态设计:所有选中节点中 深度最小的节点深度至少为 d d d 的最大权值和 ,绿色节点深度 d ′ = 2 d'=2 d′=2 显然不满足 d ′ ≥ d = 3 d' \geq d=3 d′≥d=3 那么我们此时便只能继续选黄色点而不能向上选绿色节点,即 d p [ u ] [ 3 ] = max { d p [ v ] [ 2 ] + d p [ v ′ ′ ] [ 2 ] + d p [ v ′ ] [ 2 ] } dp[u][3] = \max\{dp[v][2]+dp[v''][2]+dp[v'][2]\} dp[u][3]=max{dp[v][2]+dp[v′′][2]+dp[v′][2]} 。
再看回这个方程,浅显易懂了。
d
p
[
v
]
[
d
−
1
]
+
∑
v
′
∈
s
o
n
u
∖
{
v
}
d
p
[
v
′
]
[
max
(
d
−
1
,
k
−
d
)
]
\begin{array}{c} dp[v][d-1]+\sum_{v' \in son_u∖\{v\}}dp[v'][\max(d-1,k-d)] \end{array}
dp[v][d−1]+∑v′∈sonu∖{v}dp[v′][max(d−1,k−d)]
回到题目中,我们要的并不是所选节点深度恰好为 d d d 的最大值,而是深度 至少 为 d d d 的最大值。
但是显然,我们在深度浅的子树中是可以取深度更大的节点的,而不是必须取深度为 d d d 的节点,那么我们在最后只要再倒着从 d = n → 0 d=n \rightarrow 0 d=n→0 取 max { d p [ u ] [ d ] } \max\{dp[u][d]\} max{dp[u][d]} 即可获得 至少 的最大值了。
我们最终答案会在 d p [ 1 ] [ 0 ] dp[1][0] dp[1][0] 中取得。
具体细节看代码实现即可。
时间复杂度: O ( n 3 ) O(n^3) O(n3)
AC代码:
#include <bits/stdc++.h>
#define NO return void(cout << "No\n")
#define YES return void(cout << "Yes\n")
using namespace std;
using i64 = long long;
using PII = pair<int, int>;
const int N = 200 + 10;
i64 dp[N][N], a[N], n; int k;
vector<int> g[N];
void DFS(int u, int ufa) {
dp[u][0] = a[u];
//先将子树的 dp[v][d] 求出
for (auto& v : g[u]) {
if (v == ufa) continue;
DFS(v, u);
}
//当 d == 0 时必选根节点
for (auto& v : g[u]) {
if (v == ufa) continue;
dp[u][0] += dp[v][k];
}
//否则 d != 0 我们会取子树中的节点
for (int d = 1; d <= n; ++d) {
//先取一个 v 子树中的点
for (auto& v : g[u]) {
if (v == ufa) continue;
i64 sum = dp[v][d - 1];
//再从剩下的子树 v2 中取出能够满足的点
for (auto& v2 : g[u]) {
if (v2 == v || v2 == ufa) continue;
sum += dp[v2][max(d - 1, k - d)];
}
//在所有选取 v 的方案中取一个最大值
dp[u][d] = max(dp[u][d], sum);
}
}
//深度小的子树中可以取深度大的节点
//倒着更新一下小深度可以取得的最大值
for (int d = n - 1; d > 0; --d) {
dp[u][d - 1] = max(dp[u][d - 1], dp[u][d]);
}
}
void solve() {
cin >> n >> k;
for (int i = 1; i <= n; ++i) {
cin >> a[i];
}
for (int i = 1; i <= n - 1; ++i) {
int u, v; cin >> u >> v;
g[u].emplace_back(v);
g[v].emplace_back(u);
}
DFS(1, 0);
cout << dp[1][0] << '\n';
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int t = 1; //cin >> t;
while (t--) solve();
return 0;
}