删:思路很新奇的一道 DP 题。
通常做树形 DP 都是自底向上进行 DP 的,而此题因为转移与 DFS 序有关,所以可以拍在 DFS 序上 DP。
观察删除的性质,发现一个点 \(u\) 要么被删掉,不进行匹配,要么就必须要与 \(\bm{v\to \operatorname{lca}_{u, v}}\) 的链上(不含 \(\bm{\operatorname{lca}_{u,v}}\))的一个节点进行匹配。
同时使用虚树可以证明一个经典结论:将关键点按 DFS 序排序后,任意相邻两点的路径长度之和等于其最小斯坦纳树边权和的两倍。
这就对暴力 DP 提供了复杂度的保证,只要我们一直对两条在 DFS 序上连续的链进行 DP 即可。处理相邻的链可以在 DP 开始前对树进行 DFS,然后将相邻叶子结点的路径记录下来。
于是设计 DP:\(dp_i\) 表示走到第 \(i\) 个点时的最大权值。转移的时候对于一条路径 \((u, v)\),先找到路径上深度最小的节点 \(root\),然后把链分为两部分:\(v\to root\)、\(root \to u\)。我们需要转移的是 \(root \to u\) 的部分,因此枚举 \(root \to u\) 的节点 \(x\):
- 枚举 \(v\to root\) 中的转移节点 \(y\),当 \(x\) 的前缀最大值大于 \(y\) 的前缀最大值时,叶子之间的负担由 \(root \to u\) 的部分决定,因此转移 \(y\) 的 DP 前缀最大值即可。
- 当 \(x\) 的前缀最大值小于等于 \(y\) 的前缀最大值时,叶子之间的负担由 \(v\to root\) 的部分决定,因此转移 \(y\) 的 DP 后缀最大值(需要减掉每个 \(\bm y\) 的前缀最大值)即可。
直接枚举 \(y\) 是 \(O(n^2)\) 的。但是注意到每一部分的前缀最大值单调不降,因此可以用双指针 + 后缀最大值优化 DP 的枚举过程,时间复杂度为 \(O(n)\)。
为了方便实现,代码里使用了欧拉序进行链划分和 DP。
#include <bits/stdc++.h>
#define fi first
#define se second
#define eb(x) emplace_back(x)
#define pb(x) push_back(x)
#define lc(x) (tr[x].ls)
#define rc(x) (tr[x].rs)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ldb;
using pi = pair<int, int>;
const int N = 300005, inf = 0x3f3f3f3f;
int n, a[N], dep[N], val[N], lstleaf, L[N], R[N], ccnt, dp[N], premx[N], sufmx[N], ans = -inf;
int euler[N], ecnt;
vector<int> g[N];
void dfs(int u, bool fc)
{euler[++ecnt] = u;val[ecnt] = a[u];if(fc) dp[u] = a[u];for(auto v : g[u]){dep[v] = dep[u] + 1;dfs(v, (fc & (g[u][0] == v)));euler[++ecnt] = u;val[ecnt] = a[u];}if(g[u].size() == 0){euler[++ecnt] = u;val[ecnt] = a[u];++ccnt;L[ccnt] = lstleaf;R[ccnt] = ecnt - 1;lstleaf = ecnt;}
}
int main()
{ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);cin >> n;for(int i = 1; i <= n; i++){int x;cin >> a[i] >> x;while(x--){int v;cin >> v;g[i].push_back(v);}}memset(dp, -0x3f, sizeof(dp));dfs(1, 1);// DPfor(int i = 2; i <= ccnt; i++){int root = L[i];// 求出这条链的根for(int j = L[i] + 1; j <= R[i]; j++){if(dep[euler[j]] < dep[euler[root]]) root = j;}// 预处理左链的前缀最大值premx[root] = val[root];for(int j = root - 1; j >= L[i]; j--)premx[j] = max(premx[j + 1], val[j]);// 预处理左链的 DP 后缀最大值sufmx[L[i]] = dp[euler[L[i]]] - premx[L[i] + 1];for(int j = L[i] + 1; j <= root - 1; j++)sufmx[j] = max(sufmx[j - 1], dp[euler[j]] - premx[j + 1]);// 预处理右链的前缀最大值premx[root] = val[root];for(int j = root + 1; j <= R[i]; j++)premx[j] = max(premx[j - 1], val[j]);// 枚举右链,转移 DPint p = root - 1, mxdp = dp[euler[root - 1]];for(int j = root + 1; j <= R[i]; j++){while(p > L[i] && premx[p] <= premx[j - 1]){p--;mxdp = max(mxdp, dp[euler[p]]);}dp[euler[j]] = max(dp[euler[j]], mxdp + val[j] - premx[j - 1]);if(p - 1 >= L[i]) dp[euler[j]] = max(dp[euler[j]], sufmx[p - 1] + val[j]);}}int now = 1;while(1){ans = max(ans, dp[now]);if(g[now].size() == 0) break;now = g[now][g[now].size() - 1];}cout << ans;return 0;
}