题解:P11259 [GDKOI2023 普及组] 海星

· · 题解

树形 dp。

一个点有三种状态:

我们发现一个点为边缘节点时,该点对应的中心节点可能是它的子节点之一也可能是它的父节点,所以要把这两种情况分开讨论。

对于每个点 u,设 f_{u,0} 表示该点不属于任何海星,f_{u,1} 表示该点为边缘节点,且该点对应的中心节点是它的儿子之一,f_{u,2} 表示该点是中心节点,且选择的边缘节点仅有该点的子节点,f_{u,3} 表示该点是中心节点,且选择的边缘节点中包含该点的父节点。没有设一个东西表示“该点为边缘节点,对应的中心节点是父节点”的原因是可以从确定该点父节点是中心节点之后,直接从该点不属于任何海星的状态转移。

考虑转移:

显然,f_{u,0}=\sum_{v\in son_u} \max(f_{v,0},f_{v,1},f_{v,2}),因为该点不属于任何海星,所以该点必然不是一个中心节点,故不可以从 f_{v,3} 转移。

根据状态定义可以看出,f_{u,1} 需要从 f_{v,3} 转移,因为每个点只能对应一个中心节点,所以我们必须从所有子节点中选择一个vf_{v,3} 转移,其余子节点同上。实现时,可以先令 f_{u,1}=\sum_{v\in son_u} \max(f_{v,0},f_{v,1},f_{v,2}),最后加上 \max_{v\in son_u} (f_{v,3} - \max(f_{v,0},f_{v,1},f_{v,2})) 即可。

$f_{u,3}$ 与 $f_{u,2}$ 类似,只需要在 $f_{u,2}$ 的基础上额外考虑一下父节点的贡献即可。 ```cpp #include <bits/stdc++.h> using namespace std; namespace z { #define int long long const int N = 1e5 + 5, inf = 1e18; vector<int> p[N]; int n, a[N], f[N][4]; void dfs(int u, int fa) { int mx = -inf, val1 = a[u], val2 = -a[u]; vector<int> v1, v2; for(int v : p[u]) { if(v == fa) continue; dfs(v, u); int tmp = max({f[v][0], f[v][1], f[v][2]}); f[u][0] += tmp; f[u][1] += tmp; mx = max(mx, f[v][3] - tmp); v1.push_back(f[v][0] - tmp - a[v]); v2.push_back(f[v][0] - tmp + a[v]); } sort(v1.begin(), v1.end(), greater<int>()); sort(v2.begin(), v2.end(), greater<int>()); if(p[u].size() > (bool)fa) { val1 += f[u][0], val2 += f[u][0]; f[u][1] += mx; val1 += v1[0], val2 += v2[0]; for(int i = 1; i < (int)v1.size(); i++) if(v1[i] > 0) val1 += v1[i]; for(int i = 1; i < (int)v2.size(); i++) if(v2[i] > 0) val2 += v2[i]; f[u][2] = max(val1, val2); f[u][3] = max(val1 - a[fa], val2 + a[fa]); } else f[u][3] = abs(a[fa] - a[u]); } void main() { ios::sync_with_stdio(false); cin.tie(nullptr);cout.tie(nullptr); cin >> n; for(int i = 1; i <= n; i++) cin >> a[i]; for(int i = 1; i < n; i++) { int u, v; cin >> u >> v; p[u].push_back(v); p[v].push_back(u); } dfs(1, 0); cout << max({f[1][0], f[1][1], f[1][2]}) << '\n'; } #undef int } int main() { z::main(); return 0; } ```