恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

【题解】P4719 【模板】动态 DP(线段树版本)

  • 首页
  • 资讯中心
  • /
  • 【题解】P4719 【模板】动态 DP(线段树版本)

相关资讯

Windows平台C/C++项目pthread环境搭建与配置实战指南 2026/8/14 6:04:50
Next.js 正在成为 Vibe Coding 时代的“通用语” 2026/8/14 5:59:50
从银狐恶意软件看现代终端安全:多层防御体系构建指南 2026/8/14 5:59:50

最新资讯

Go语言数据类型全解析:For-learning-Go-Tutorial带你掌握基本类型与复合类型
为什么选择bert-portuguese-ner?97%准确率的葡萄牙语NER模型深度测评
零成本体验SolidWorks:一份写给新手的完整上手路线图
揭秘Fixer架构:Linear Diffusion Transformer如何实现0.6B参数高效图像修复
企业系统数据交互实战:从文件对接到消息队列的架构选型指南
Mole:磁盘告警到一键释放 50GB,macOS 终端清理工具的安全链路拆解

今日推荐

青岛煜鹏网站建设公司如何帮助传统企业实现数字化转型破局与增长路径
内蒙古生产建设兵团四师三十四团知青网站:承载岁月记忆与青春荣耀的精神家园
梅州市住房与城乡建设局官网:获取权威建筑信息、政策解读与民生服务的最佳平台入口

本周热门

5分钟告别提取码焦虑:baidupankey如何智能破解百度网盘资源锁
如何快速生成中国车牌图片:Python开源工具完整指南
当 LLM 遇见大文档:主流开源项目如何处理上下文超限

本月精选

如何用DamaiHelper实现演唱会门票的智能自动化抢购:完整技术解决方案指南
第4篇:59 倍性能差距的索引瓶颈定位——一次教科书级的全表扫描调优
终极歌词批量下载神器:5分钟解决离线音乐库歌词同步难题

【题解】P4719 【模板】动态 DP(线段树版本)

发布时间:2026/8/14 6:04:50
【题解】P4719 【模板】动态 DP(线段树版本) P4719 【模板】动态 DP - 洛谷动态 dp新东西猫娘学习。希望我贫瘠的线性代数知识能帮到你。0.导入不考虑修改操作这只是简单的树上 dp即没有上司的晚会。但每次修改单个点权如果重新跑整棵树 DP 是的无法满足大数据。我们希望能支持单点修改并快速获得根的状态。常方法是对树做轻重链剖分。考虑到 DP 转移是固定的把每个节点的 DP 转移写成线性变换用矩阵表示。从而一条重链上的 DP 可以用线段树维护矩阵乘积。修改一个点权时只更新该点到根路径上涉及的重链的线段树实际是“跳链”更新。1.矩阵相关可能有人对矩乘还不熟悉那我这里再解释下。中第一行第一列的配上第一行第一列的可以贡献第一行第一列的。中第一行第二列的配上第二行第一列的可以贡献第一行第一列的。中第二行第一列的配上第一行第一列的可以贡献第二行第一列的。中第二行第二列的配上第二行第一列的可以贡献第二行第一列的。2.重链内维护我们把每个点按照重链剖分后给每条重链分配一个线段树线段树节点存储该区间内矩阵的乘积按链顺序从浅到深相乘。对于叶子节点重儿子不存在我们定义它的“重儿子”矩阵为单位矩阵。每个点维护自己的矩阵然后一条重链的顶端节点向上传给父节点时用该链的矩阵乘积得到顶端的 DP 值进而更新父节点的轻儿子贡献。当修改点的权值为更新点的矩阵​ 中的然后从开始沿着重链向上更新对于当前节点重新计算它所在重链的线段树得到这条重链顶端的 DP 向量。如果这条链的顶端是那么的父节点的轻儿子贡献可能发生变化因此需要更新的​ 或​。然后继续向上处理父节点所在的重链直到根。每次修改涉及的链数量是因为轻边数量是。每条链上的线段树更新是所以总复杂度。3.代码#includebits/stdc.h using namespace std; const int N 1e5 10; const int inf 0x3f3f3f3f; #define lc(p) (p 1) #define rc(p) ((p 1) | 1) #define mid ((l r) 1) int n, m; int a[N]; vectorint G[N]; int fa[N], siz[N], son[N]; // 父亲、子树大小、重儿子 int f[N][2]; // 静态DPf[x][0/1] 表示x不选/选时的子树最大权 int dfn[N], id[N], top[N], bot[N], tsp; // dfn[x]节点 x 的 dfs 序时间戳id[t]dfs序 t 对应的节点编号 // top[x]x 所在重链的链头节点bot[top]链头 top 所在链的链尾节点的 dfs 序即链的区间右端点 // tspdfs 序计数器 // 第一次 DFS处理 fa, siz, son并计算静态 DP 初值 void dfs(int x) { siz[x] 1; f[x][1] a[x]; // 选 x初始贡献为自己的权值 for (int y : G[x]) if (fa[x] ! y) { fa[y] x; dfs(y); siz[x] siz[y]; if (siz[y] siz[son[x]]) { son[x] y; // 更新重儿子 } // 正常树上 DP 转移 f[x][0] max(f[y][0], f[y][1]); // x 不选儿子可选可不选 f[x][1] f[y][0]; // x 选儿子必须不选 } } // 第二次 DFS树链剖分分配 dfs 序确定 top 和 bot void dfs(int x, int tp) { dfn[x] tsp; id[tsp] x; top[x] tp; bot[tp] tsp; // 更新链尾的 dfn因为 dfs 序连续最后遍历到的就是链尾 if (son[x]) { dfs(son[x], tp); // 优先重儿子保持链上dfs序连续 } for (int y : G[x]) if (y ! fa[x] y ! son[x]) { dfs(y, y); // 轻儿子开新链 } } // ---------- 矩阵结构体用于动态DP的转移矩阵 ---------- struct Matrix { int g[2][2]; // 重载乘法广义矩阵乘法max-plus Matrix operator*(Matrix b) { Matrix t; memset(t.g, -0x3f, sizeof(t.g)); for (int i 0; i 1; i ) for (int j 0; j 1; j ) for (int k 0; k 1; k ) t.g[i][j] max(t.g[i][j], g[i][k] b.g[k][j]); return t; } } mt[N], tr[N 2]; // mt[x]节点x的转移矩阵仅考虑轻儿子贡献 // tr[p]线段树节点维护一段 dfs 序区间的矩阵乘积 void pushup(int p) { tr[p] tr[lc(p)] * tr[rc(p)]; } void build(int p, int l, int r) { if (l r) { int x id[l]; // 当前dfs序对应的节点 int g0 0, g1 a[x]; // g0: x 不选g1: x 选 // 遍历轻儿子非重儿子、非父亲累加其DP值 for (auto y : G[x]) { if (y ! fa[x] y ! son[x]) { g0 max(f[y][0], f[y][1]); // x 不选时轻儿子任意 g1 f[y][0]; // x 选时轻儿子不选 } } // 构造转移矩阵 // g[0][0] g[0][1] g0 不选 x 时当前链状态任意下一状态也是不选 // g[1][0] g1 选 x下一状态必须不选 // g[1][1] -inf 选 x 后不能转移到选因为相邻不能同时选 tr[p] mt[x] {g0, g0, g1, -inf}; return ; } build(lc(p), l, mid); build(rc(p), mid 1, r); pushup(p); } // 线段树单点更新 void change(int p, int l, int r, int x) { if (x l || r x) { return ; } if (l r) { tr[p] mt[id[l]]; return; } change(lc(p), l, mid, x); change(rc(p), mid 1, r, x); pushup(p); } // 线段树区间查询返回区间 [x, y] 的矩阵乘积 Matrix query(int p, int l, int r, int x, int y) { if (x l r y) { // 必须刚刚好 return tr[p]; } if (y mid) return query(lc(p), l, mid, x, y); if (x mid) return query(rc(p), mid 1, r, x, y); // 跨区间先左后右注意乘法顺序左乘右 return query(lc(p), l, mid, x, mid) * query(rc(p), mid 1, r, mid 1, y); } // 动态 DP 核心修改点权并更新所有影响 void update(int u, int k) { // 更新点 u 的转移矩阵中关于 “选 u” 的项轻儿子贡献不变只变自身权值 mt[u].g[1][0] k - a[u]; a[u] k; // 更新权值 // 从 u 开始沿重链向上更新直到根 while (u) { // 先查询当前 u 所在链的旧矩阵乘积整条链的转移 Matrix old query(1, 1, n, dfn[top[u]], bot[top[u]]); // 将 u 的叶子节点在线段树中更新此时 mt[u] 已变 change(1, 1, n, dfn[u]); // 查询更新后该链的新矩阵乘积 Matrix now query(1, 1, n, dfn[top[u]], bot[top[u]]); // 当前链的矩阵变化会影响其父链当前链头作为父节点的一个轻儿子 // 因此需要更新父节点即链头 top[u] 的父亲的轻儿子贡献。 u fa[top[u]]; // 跳转到父链的节点即当前链头的父亲 if (!u) break; // 到达根的上方结束 // 根据新旧链矩阵的DP结果差值更新父节点u的轻儿子贡献 // old 和 now 的 g[0][0] 和 g[1][0] 分别代表该链头节点不选/选时的最大权 // 父节点 u 不选时轻儿子可任意 // 所以要加上 max(now.g[0][0], now.g[1][0]) - max(old.g[0][0], old.g[1][0]) mt[u].g[0][0] max(now.g[0][0], now.g[1][0]) - max(old.g[0][0], old.g[1][0]); mt[u].g[0][1] mt[u].g[0][0]; // g[0][0] 和 g[0][1] 相同因为不选时下一状态任意 // 父节点 u 选时该轻儿子必须不选所以变化量为 now.g[0][0] - old.g[0][0] mt[u].g[1][0] now.g[0][0] - old.g[0][0]; // 注意mt[u].g[1][1] 始终为 -inf保持不变 } } int main() { ios::sync_with_stdio(false); cin.tie(0); cin n m; for (int i 1; i n; i ) { cin a[i]; } for (int i 1; i n; i) { int x, y; cin x y; G[x].push_back(y); G[y].push_back(x); } fa[1] 0; tsp 0; memset(son, 0, sizeof(son)); dfs(1); // 第一次 DFS求 fa, size, son, 静态 DP dfs(1, 1); // 第二次 DFS树链剖分分配 dfn build(1, 1, n); while (m --) { int x, y; cin x y; update(x, y); Matrix ans query(1, 1, n, dfn[1], bot[1]); // 查询整棵树根 1 所在链的矩阵 cout max(ans.g[0][0], ans.g[1][0]) \n; } return 0; }

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号