树形DP进阶:结合dfn序的线性化树问题求解技巧

树形DP进阶:结合dfn序的线性化树问题求解技巧

    • 一、dfn序与树的线性化
      • 1.1 dfn序的基本概念
      • 1.2 树形DP结合dfn序的优势
    • 二、核心应用:子树区间的DP优化
      • 2.1 子树权值和的快速查询与更新
        • 问题描述
        • 结合dfn序的解法
        • 代码实现(前缀和版本)
        • 优化说明
      • 2.2 树形DP与线性DP的结合(子树序列拼接)
        • 问题描述
        • 结合dfn序的解法
        • 代码实现
        • 核心思路
    • 三、进阶技巧:dfn序与树形DP的状态融合
      • 3.1 带限制的子树节点选择(结合线段树)
        • 问题描述
        • 解法思路
        • 关键代码
      • 3.2 dfn序在多子树合并中的应用
    • 四、dfn序的优化边界
      • 4.1 适用场景
      • 4.2 局限性
      • 总结

树形DP求解中,我们常遇到需要频繁处理“子树区间查询”“多子树合并”或“树与线性结构交互”的问题,这类问题单纯依靠递归遍历往往难以高效实现,而dfn序(深度优先搜索序号) 作为将树结构转化为线性结构的桥梁,能显著降低问题复杂度。

一、dfn序与树的线性化

1.1 dfn序的基本概念

dfn序(Depth-First Numbering)是对树进行深度优先搜索(DFS)时,记录节点首次被访问的时间戳。它将树的层级结构转化为线性数组,同时具有一个关键特性:任意子树在dfn序中对应一个连续的区间

  • 具体来说,对节点u进行DFS时,记录:
    • dfn[u]:节点u的入栈时间(首次访问序号);
    • size[u]:以u为根的子树包含的节点数;
    • u的子树在dfn序中对应区间为[dfn[u], dfn[u] + size[u] - 1]

二叉树示例:

    1/ \2   3/ \4   5

DFS访问顺序为1→2→2(回退)→3→4→4(回退)→5→5(回退)→3(回退)→1(回退),dfn序为:

  • dfn[1]=1size[1]=5 → 子树区间[1,5]
  • dfn[2]=2size[2]=1 → 子树区间[2,2]
  • dfn[3]=3size[3]=3 → 子树区间[3,5]
  • dfn[4]=4size[4]=1 → 子树区间[4,4]
  • dfn[5]=5size[5]=1 → 子树区间[5,5]

1.2 树形DP结合dfn序的优势

传统树形DP通过递归处理子树,在以下场景中存在局限:

  • 需要多次查询子树信息(如“统计子树中某属性的和”);
  • 合并多个子树的线性结构(如“将子树的序列拼接后进行DP”);
  • 结合线段树、前缀和等线性数据结构优化查询。

而dfn序的线性化特性可解决这些问题:

  1. 子树区间化:将子树转化为连续区间,使子树查询等价于区间查询;
  2. 线性结构复用:可直接使用前缀和、线段树等处理线性数据的工具;
  3. 状态转移简化:多子树的合并可转化为区间拼接,降低递归嵌套复杂度。

二、核心应用:子树区间的DP优化

2.1 子树权值和的快速查询与更新

问题描述

给定一棵带权树,支持两种操作:

  1. 更新某节点的权值;
  2. 查询以u为根的子树的权值和。

要求高效实现这两种操作(传统递归查询每次需O(size[u]),效率低)。

结合dfn序的解法
  1. 线性化映射

    • 对树进行DFS,计算每个节点的dfn[u]size[u],子树u对应区间[L, R] = [dfn[u], dfn[u] + size[u] - 1]
    • 将节点权值按dfn序存入数组val[dfn[u]] = w[u]
  2. 前缀和/线段树优化

    • 用前缀和数组pre维护val的区间和,pre[i] = val[1] + ... + val[i]
    • 子树和查询:sum(u) = pre[R] - pre[L-1]
    • 单点更新:修改val[dfn[u]]后同步更新前缀和(或用线段树实现O(log n)更新)。
代码实现(前缀和版本)
import java.util.*;public class SubtreeSumWithDFN {List<List<Integer>> adj;int[] w; // 节点权值int[] dfn, size; // dfn序和子树大小long[] pre; // 前缀和数组int n, dfnCnt;public SubtreeSumWithDFN(int n, int[] w) {this.n = n;this.w = w;adj = new ArrayList<>();for (int i = 0; i < n; i++) {adj.add(new ArrayList<>());}dfn = new int[n];size = new int[n];dfnCnt = 0;}public void addEdge(int u, int v) {adj.get(u).add(v);adj.get(v).add(u);}// 计算dfn序和size(根节点设为0)private void dfs(int u, int parent) {dfn[u] = dfnCnt++;size[u] = 1;for (int v : adj.get(u)) {if (v == parent) continue;dfs(v, u);size[u] += size[v];}}// 初始化前缀和public void build() {dfs(0, -1);pre = new long[n + 1]; // pre[0] = 0, pre[1] = val[0], ...for (int u = 0; u < n; u++) {pre[dfn[u] + 1] = pre[dfn[u]] + w[u]; // dfn从0开始,前缀和从1开始}}// 查询子树u的权值和public long querySubtreeSum(int u) {int L = dfn[u];int R = dfn[u] + size[u] - 1;return pre[R + 1] - pre[L];}// 更新节点u的权值(delta为变化量)public void update(int u, int delta) {w[u] += delta;int pos = dfn[u];for (int i = pos + 1; i <= n; i++) { // 前缀和更新(低效,实际用线段树)pre[i] += delta;}}public static void main(String[] args) {int n = 5;int[] w = {10, 20, 30, 40, 50}; // 节点0~4的权值SubtreeSumWithDFN tree = new SubtreeSumWithDFN(n, w);tree.addEdge(0, 1); // 节点0对应示例中的1tree.addEdge(0, 2);tree.addEdge(2, 3);tree.addEdge(2, 4);tree.build();System.out.println(tree.querySubtreeSum(2)); // 子树2包含节点2,3,4 → 30+40+50=120tree.update(3, 10); // 节点3权值+10 → 50System.out.println(tree.querySubtreeSum(2)); // 30+50+50=130}
}
优化说明
  • 上述代码的更新操作是O(n),实际应用中应改用线段树树状数组,将更新和查询复杂度均降至O(log n)
  • 核心思想是利用dfn序的区间特性,将“子树查询”转化为“区间查询”,彻底摆脱递归遍历的低效。

2.2 树形DP与线性DP的结合(子树序列拼接)

问题描述

给定一棵二叉树,每个节点有一个字符,求所有子树对应的字符串中,最长回文子序列的长度。

  • 示例:节点3的子树包含字符'a''b''a',对应字符串"aba",最长回文子序列长度为3。
结合dfn序的解法
  1. 子树字符串的线性化

    • 对树进行DFS,按dfn序记录节点字符,得到数组chars,子树u的字符串对应chars[L..R]L=dfn[u], R=dfn[u]+size[u]-1)。
  2. 区间DP预处理

    • 预处理线性数组chars的区间回文子序列长度lps[L][R](经典区间DP,lps[L][R]表示chars[L..R]的最长回文子序列长度)。
  3. 树形DP映射

    • 对每个节点u,其最长回文子序列长度即为lps[L][R],直接通过dfn区间查询获取。
代码实现
import java.util.*;public class SubtreePalindromeWithDFN {List<List<Integer>> adj;char[] chars; // 节点字符int[] dfn, size;int[][] lps; // 区间最长回文子序列int n, dfnCnt;public SubtreePalindromeWithDFN(int n, char[] chars) {this.n = n;this.chars = chars;adj = new ArrayList<>();for (int i = 0; i < n; i++) {adj.add(new ArrayList<>());}dfn = new int[n];size = new int[n];dfnCnt = 0;lps = new int[n][n];}public void addEdge(int u, int v) {adj.get(u).add(v);}// 计算dfn序和size(二叉树,左子树优先)private void dfs(int u) {dfn[u] = dfnCnt++;size[u] = 1;for (int v : adj.get(u)) { // 假设子节点按左右顺序存储dfs(v);size[u] += size[v];}}// 预处理区间最长回文子序列private void preprocessLPS() {// 区间DP:lps[L][R] = 最长回文子序列长度for (int i = n - 1; i >= 0; i--) {lps[i][i] = 1; // 单个字符for (int j = i + 1; j < n; j++) {if (chars[i] == chars[j]) {lps[i][j] = (j == i + 1) ? 2 : lps[i + 1][j - 1] + 2;} else {lps[i][j] = Math.max(lps[i + 1][j], lps[i][j - 1]);}}}}// 获取子树u的最长回文子序列长度public int getMaxPalindrome(int u) {int L = dfn[u];int R = dfn[u] + size[u] - 1;return lps[L][R];}public static void main(String[] args) {int n = 3;char[] chars = {'a', 'b', 'a'}; // 节点0: 'a', 节点1: 'b', 节点2: 'a'SubtreePalindromeWithDFN tree = new SubtreePalindromeWithDFN(n, chars);tree.addEdge(0, 1); // 0是根,1和2是子节点(模拟二叉树0->1->2)tree.addEdge(1, 2);tree.dfs(0);tree.preprocessLPS();System.out.println(tree.getMaxPalindrome(0)); // 子树0: "aba" → 3System.out.println(tree.getMaxPalindrome(1)); // 子树1: "ba" → 1}
}
核心思路
  • 通过dfn序将“子树字符串”转化为“线性区间”,复用区间DP的结果,避免对每个子树单独计算回文序列(传统方法复杂度O(n^3),优化后为O(n^2))。
  • 体现了“树的线性化”与“线性DP预处理”结合的高效性,尤其适合子树操作频繁的场景。

三、进阶技巧:dfn序与树形DP的状态融合

3.1 带限制的子树节点选择(结合线段树)

问题描述

给定一棵树上每个节点有价值v[u]和颜色c[u],求一个子树u,使得:

  • 子树中所有节点的颜色均为k
  • 子树的总价值最大。
解法思路
  1. dfn序与颜色过滤

    • 按dfn序遍历树,记录每个颜色k对应的节点在dfn数组中的位置,形成colorPos[k] = [p1, p2, ...](升序排列)。
  2. 区间最大值查询

    • 用线段树维护dfn序的价值前缀和,对颜色k,在colorPos[k]中查找连续区间(对应子树),计算其价值和并取最大值。
  3. 子树验证

    • 对颜色k的每个连续区间[L, R],检查是否存在节点u使得dfn[u] = Ldfn[u] + size[u] - 1 = R(即该区间是合法子树)。
关键代码
// 检查区间[L, R]是否为合法子树
private boolean isSubtree(int L, int R) {int u = findNodeByDFN(L); // 根据dfn值找到节点u(需维护dfn到节点的映射)return (dfn[u] + size[u] - 1) == R;
}// 查找颜色k的最大子树价值
public int maxSubtreeValue(int k) {List<Integer> positions = colorPos.get(k);int maxSum = 0;// 遍历颜色k的所有节点位置,检查连续区间是否为子树for (int i = 0; i < positions.size(); i++) {int L = positions.get(i);for (int j = i; j < positions.size(); j++) {int R = positions.get(j);if (!isSubtree(L, R)) continue;int sum = segmentTree.query(L, R); // 线段树查询区间和maxSum = Math.max(maxSum, sum);}}return maxSum;
}

3.2 dfn序在多子树合并中的应用

当需要合并多个子树的DP状态时(如“选择k个不相交子树,使总价值最大”),dfn序的线性特性可将问题转化为“在数组中选择k个不重叠区间,使区间和最大”,直接复用线性DP的解法:

  • 定义dp[i][j]表示“前i个dfn位置,选择j个子树的最大价值”;
  • 转移时判断当前位置是否为子树起点,若是则可选择包含该子树(dp[i+size[u]-1][j+1] = max(dp[i+size[u]-1][j+1], dp[i-1][j] + sum(u)))。

四、dfn序的优化边界

4.1 适用场景

  • 子树区间查询频繁:如子树和、子树最值、子树序列属性(回文、递增等);
  • 树与线性结构交互:需结合前缀和、线段树、滑动窗口等线性算法;
  • 多子树合并问题:子树的选择、组合需满足线性约束(如不重叠、连续等)。

4.2 局限性

  • 动态树不适用:树结构频繁修改(如节点插入/删除)会导致dfn序失效,需用动态树数据结构(如Link-Cut Tree);
  • 非DFS友好的问题:依赖广度优先遍历(BFS)特性的问题(如层次相关),dfn序优势不明显;
  • 区间映射 overhead:对简单子树问题(如单次查询),递归遍历可能比dfn序预处理更高效。

总结

dfn序作为树结构线性化的核心工具,为树形DP提供了“降维打击”的思路——将复杂的树状问题转化为直观的线性问题,从而复用成熟的线性数据结构与算法:

  1. 子树区间化:通过dfn[u]size[u]将子树映射为连续区间,简化查询;
  2. 线性工具复用:前缀和、线段树等可直接应用于子树问题,提升效率;
  3. 状态融合:使树形DP与线性DP的状态转移无缝衔接,解决多子树合并等复杂场景。

掌握dfn序技巧的关键:

  • 熟练计算和应用dfnsize数组;
  • 敏感识别“子树问题可转化为区间问题”的场景;
  • 结合具体问题选择合适的线性数据结构(前缀和、线段树等)。

That’s all, thanks for reading~~
觉得有用就点个赞、收进收藏夹吧!关注我,获取更多干货~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/91853.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/91853.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

九、Maven入门学习记录

Maven介绍Maven作用统一项目结构Maven安装&#xff08;注意配置阿里云私服时url要跟换成最新的&#xff09;IDEA创建Meavn项目Maven坐标介绍IDEA导入Maven项目依赖配置依赖传递依赖传递-排除依赖依赖范围生命周期生命周期-执行特定生命周期生命周期-总结

中标喜讯 | 安畅检测再下一城!斩获重庆供水调度测试项目

安畅检测在第三方检测领域持续深耕&#xff0c;再传捷报&#xff01;公司于2025年7月30日正式收到中标通知&#xff0c;成功拿下重庆水资源产业股份有限公司 “重庆西部科学城多水厂分区分压供水优化调度研究项目&#xff08;软件测试标段&#xff09;”。 此次中标不仅是市场…

银河麒麟V10一键安装DM8的脚本及高阶运维SQL分享

介质下载地址名称网址银河麒麟高级服务器操作系统V10&#xff08;SP3&#xff09;用户手册https://www.kylinos.cn/support/document/60.htmlDM8 安装手册https://eco.dameng.com/document/dm/zh-cn/pm/install-uninstall.htmlDM 数据库安装&#xff08;Linux安装&#xff09;h…

cobalt strike(CS)与Metasploit(MSF)联动

CS —> MSF首先cs上创建一个http的外部监听器。此时在CS服务端查看监听的ip&#xff0c;发现并没有开启&#xff0c;需要到成功移交会话后才会启动。netstat -tunlp | grep 7000在MSF中使用handler模块&#xff0c;配置监听。注意&#xff1a;目标机器的地址是rhost&#xf…

C# 类型

原文&#xff1a;C# 类型_w3cschool C#类型 类型定义值的蓝图。有不同的操作与不同类型相关联。 在下面的示例中&#xff0c;我们使用两个类型为int的常量&#xff0c;值为2 和 3。 static void Main() {int x 2 * 3;Console.WriteLine (x); } int 是一个表示整数值的构建…

确保TDesign Vue Next中t-color-picker组件在弹出颜色拾取面板时保证该面板不抖动方法参考

使用TDesign Vue Next中的组件t-color-picker时&#xff0c;在颜色面板弹出后&#xff0c;如果修改里面的颜色&#xff0c;发现这个颜色拾取面板会随着颜色的改变位置不断抖动&#xff0c;该问题由显示颜色的数值文本的长度变化引起&#xff0c;因此要覆盖组件内部颜色值文本的…

bypass

代码解析修改自身bypass&#xff1a;第一句话$s"Declaring file object\n";定义一个s&#xff0c;值为Declaring file object第二句话$d$_SERVER[DOCUMENT_ROOT].$_SERVER[DOCUMENT_URI]; 不知道$_SERVER是什么&#xff0c;那就打印出来看看。输入echo <pre>;…

C语言:构造类型学习

内容提要 构造类型 枚举类型typedef 综合案例&#xff1a;斗地主 构造类型 枚举类型 建议&#xff1a;如果定义不相干的常理&#xff0c;使用宏定义&#xff08;符号常量&#xff09;&#xff1b;如果需要定义一组相关联的常量&#xff0c;如月份0~11&#xff0c;星期0~6&#…

Prometheus-3--Prometheus是怎么抓取Java应用,Redis中间件,服务器环境的指标的?

1、Prometheus抓取Java应用的指标 1、数据来源&#xff1a;Java应用自身暴露的指标 Java应用的指标数据来源于应用代码中定义的指标对象&#xff08;如Counter、Gauge、Histogram等&#xff09;&#xff0c;通过Prometheus客户端库&#xff08;如io.prometheus:client_java&…

42.安卓逆向2-补环境-unidbg安装和简单使用

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 内容参考于&#xff1a;图灵Python学院 工具下载&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1bb8NhJc9eTuLzQr39lF55Q?pwdzy89 提取码&#xff1…

数据结构与算法:哈希函数的应用及一些工程算法

前言这篇里的东西可以说了解了解就行了。一、哈希函数均匀性展示原本让deepseek转了一下老师的java代码&#xff0c;但发现复刻起来太麻烦了。又因为这个理解就好&#xff0c;竞赛不会有&#xff0c;所以就直接贴老师的java代码了……import java.security.MessageDigest; impo…

交叉编译ARM环境

ARM交叉编译 可以采用交叉编译工具链&#xff1a; sudo apt-get install aarch64-linux-gnu-gcc sudo apt-get install aarch64-linux-gnu-g sudo apt-get install gcc-arm-linux-gnueabi sudo apt-get install g-arm-linux-gnueabi 上面两个是64位&#xff0c;下面两个是…

算法思想 之 拓扑排序问题

欢迎拜访&#xff1a;雾里看山-CSDN博客 本篇主题&#xff1a;算法思想 之 拓扑排序问题 发布时间&#xff1a;2025.8.4 隶属专栏&#xff1a;算法 目录算法介绍核心原理适用场景实现步骤(Kahn 算法)例题课程表题目链接题目描述算法思路代码实现课程表 II题目链接题目描述算法思…

机器学习 入门——决策树分类

决策树是一种直观且强大的机器学习算法&#xff0c;适用于分类和回归任务。本文将全面介绍决策树分类的原理、实现、调优和实际应用。一、什么是决策树分类1.概念决策树分类是一种树形结构的分类模型&#xff0c;它通过递归地将数据集分割成更小的子集来构建决策规则。就像我们…

虚拟机中查看和修改文件权限

在虚拟机中管理文件权限是系统管理的重要部分&#xff0c;无论是在Linux还是Windows虚拟机中。下面我将详细介绍两种主要系统的权限管理方法。Linux虚拟机中的文件权限管理查看文件权限使用ls命令&#xff1a;ls -l 文件名输出示例&#xff1a;-rwxr-xr-- 1 user group 1024 Ju…

图像处理拉普拉斯算子

AI对话记录&#xff0c;还没有来得及仔细验证和推导&#xff0c;目前只是记录 当然可以&#xff01;我们来一步步推导拉普拉斯算子在旋转变换下保持不变的数学过程。这里以二维情况为例&#xff0c;最直观也最常见。&#x1f9ee; 拉普拉斯算子旋转不变性的推导&#xff08;二维…

React ahooks——副作用类hooks之useThrottleEffect

useThrottleEffect 是 ahooks 提供的节流版 useEffect&#xff0c;它在依赖项变化时执行副作用函数&#xff0c;但会限制执行频率。一、基本语法useThrottleEffect(effect: React.EffectCallback,deps?: React.DependencyList,options?: Options )二、参数详解2.1. effect (必…

【建模与仿真】融合画像约束和潜在特征的深度推荐算法

导读&#xff1a; 基于深度学习的推荐算法已成为推荐系统领域的研究趋势。然而&#xff0c;大多数现有工作仅考虑单一的用户与物品交互数据&#xff0c;限制了算法的预测性能。本文提出一种画像约束的编码方式&#xff0c;并融合隐因子模型中的潜在特征&#xff0c;丰富了推荐…

华为网路设备学习-26(BGP协议 二)路径属性

一、属性分类二、属性含义①公认必遵&#xff1a;所有BGP对等体 必须识别 且 在Update报文中携带1.Origin2.AS-Path3.Next hop②公认自决&#xff1a;所有BGP对等体 必须识别但可以不在Update报文中携带 1.Local-Preference2.ATOMIC_Aggregate③可选传递&#xff1a;所有BGP对…

从0搭建YOLO目标检测系统:实战项目+完整流程+界面开发(附源码)

文章目录一、前言二、专栏介绍三、已有系统介绍3.0 基于yolo通用目标检测系统&#xff08;手把手教你修改成为自己的检测系统&#xff09;3.1 基于yolov8柑橘检测系统3.2 基于yolov8舰船检测系统3.3 基于yolo11人脸检测系统3.4 基于yolov8无人机影像光伏板缺陷检测系统一、前言…