当前位置: 首页 > news >正文

Hetao P5593 删 题解 [ 蓝 ] [ 线性 DP ] [ DFS 序 ] [ 虚树 ]

删:思路很新奇的一道 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;
}
http://www.hskmm.com/?act=detail&tid=36098

相关文章:

  • o(N^2)找出所有回文子串
  • 第二次高级程序作业
  • 大学生需要认真听课的肌肉记忆(注意力训练)
  • 初始人工智能和机器学习
  • 10/21
  • 蛋白表达技术概述
  • 二叉树的中序遍历- 递归原理 - MKT
  • 友链测试
  • 二叉树的中序遍历- 二叉树基本-递归 - MKT
  • 做了一个概率小游戏,没想到服务器被打爆被攻击了!原因竟然是他?真没想到...
  • 接下来的目标
  • 阿里云对象存储OSS之Java - Soul
  • 敬启,致那时的我
  • 后量子密码学技术与标准化进程解析
  • 10月21日
  • 清楚标签默认样式,内容溢出盒子时的处理
  • 用 大模型 和 Gradio 构建一个 AI 反向词典
  • MySQL 事务
  • python概念详解
  • JAVA基础理解
  • 1279. 红绿灯路口
  • 软件工程第三次作业
  • 用户消费行为数据分析(随笔)
  • linux常用命令总结
  • sqlserver 主要的日期函数及用法示例
  • ICPC2022沈阳 游记(VP)
  • 大数据分析基础及应用案例:第四周学习报告——线性回归模型
  • 「LG7446-rfplca」题解
  • 图论刷题记录
  • 「LG6596-How Many of Them」题解