算法:树形dp(树状dp)

文章目录

    • 一、树形DP的概念
      • 1.基本概念
      • 2.解题步骤
      • 3.树形DP数据结构
    • 二、典型例题
      • 1.LeetCode:337. 打家劫舍 III
        • 1.1、定义状态转移方程
        • 1.2、参考代码
      • 2.ACWing:285. 没有上司的舞会
        • 1.1、定义状态转移方程
        • 1.2、拓扑排序参考代码
        • 1.3、dfs后序遍历参考代码

一、树形DP的概念

  树形动态规划(Tree DP) 是动态规划在树结构数据上的应用。树是一种特殊的图,具有无环的特点,这使得树形动态规划在很多问题中比普通的动态规划更为直观和高效。在树形DP问题中,通常需要处理与节点相关的状态,并利用树的层次结构,自底向上或自顶向下递归地解决问题。树形DP的学习对线性DP的状态定义也有很大帮助。

1.基本概念

  • 状态表示:树形DP的状态通常与树的节点相关联,每个状态代表了以某节点为根的子树在某种条件下的最优解、计数或其他性质。
  • 状态转移:状态的转移依赖于子节点到父节点(或父节点到子节点)的关系。通常,要解决一个节点的问题,需要先解决它的所有子节点的问题,然后根据子节点的解来更新当前节点的解。或者当前结点的状态由父亲转移而来。

  在树形DP中,如果当前结点的状态由其子节点转移而来,那么可以进行DFS后序遍历,或者拓扑排序顺序遍历(在图中)。比如在关键路径中,提到过,求最长路径可以使用拓扑排序+动态规划来求解,这实际上就很像是一个树形DP。

2.解题步骤

  我们说树形DP的状态转移,实际上比线性DP更直观,因为父子结点之间的关系是很明显的,是从上到下的状态转移还是从下依次往下的状态转移是很明显的。

  1. 定义状态:首先明确每个状态所代表的含义,以及解题需要考虑的所有情况。
  2. 状态转移方程:根据问题的具体条件,找出不同状态之间的关系,形成状态转移方程。这通常涉及对当前节点的处理和对子节点状态的汇总。
  3. 初始化:确定递归的基本情况,即最简单情况下各状态的值,用于终止递归。
  4. 计算顺序:确定状态计算的顺序。在树形DP中,通常需要后序遍历(先处理所有子节点,再处理父节点)来自底向上计算,或者先序遍历来自顶向下传递信息。
  5. 答案提取:根据定义的状态,从根节点或特定节点提取最终答案。

3.树形DP数据结构

线性dp直接可以使用线性数组来存储状态,那么树形DP怎么办呢?

  • 如果真的只跟其亲儿子有关,你可以直接在树上进行DFS后序,利用DFS函数中存储状态,因为这不涉及到记忆化搜索
  • 如果其不仅仅跟其亲儿子有关,你就必须采取一定行动来保存状态信息了(属于一种记忆化搜索)。
  • 你可以先进行一次DFS对树结点编号(或已经有编号),然后再操作。这样你就可以定义dp数组了
  • 你可以对树结点存储在哈希表中来存储该结点状态。这样你就可以定义dp了
  • 如果需要新建图,你可以对树或图,存储其邻接表,并且每个结点多定义一个状态信息即可。若对于一个特定的顶点状态,它跟其邻接表中所有结点有关,如果其邻接表中的顶点状态已经被求出,则可以进行转移。树图不分家。vector实现邻接表
  • ···

二、典型例题

1.LeetCode:337. 打家劫舍 III

模板题
337. 打家劫舍 III
在这里插入图片描述
  父亲最优解跟其子节点最优解有关,因此属于树状dp。由于父亲的最优解不仅跟其亲儿子有关,还跟孙子有关,因此我们在进行树状dp时,不能只DFS后序遍历返回结果,我们还得存储孙子信息,即记忆化搜索,因此我们在书写这个树状dp的时候,需要使用额外空间存储状态,比如在此题中没有给出编号,使用哈希表是最快的。
  但是以上我们所说的状态是单个状态 即 dp[root]表示以root为根的最高金额,如果我们定义的状态dp[root][0]表示不选择root时的最高金额,dp[root][1]表示选择root时的最高金额,那么我们只需要将树返回值是多个值(如结构体),就可以不用多开额外空间了~

1.1、定义状态转移方程
  • dp[root][0]=max(dp[l_child][1],dp[l_child][0])+max(dp[r_child][1],dp[r_child][0]);//不被选择时
  • dp[root][1]=root->val+dp[l_child][0]+dp[r_child][0];//被选择时
    • 根不被选择时,其能得到的最大金额的状态,由其儿子不被选择的最大金额状态以及其儿子被选择的最大金额状态转移而来,因为根不被选择时,其儿子可以被选也可以不被选,让根的金额最大,那么择其最大者就行。
    • 根被选择时,其能得到的最大金额的状态,由其儿子不被选择的最大金额状态转移而来。
1.2、参考代码
class Solution {
public://pair::first表示不被选择时的最大值//pair::second表示被选择时的最大值pair<int,int> TreeDp(TreeNode * root){if(!root) return {0,0};pair<int,int> dp_left=TreeDp(root->left);pair<int,int> dp_right=TreeDp(root->right);pair<int,int> dp_root;//不被选择时dp_root.first=max(dp_left.first,dp_left.second)+max(dp_right.first,dp_right.second);//被选择时dp_root.second=root->val+dp_left.first+dp_right.first;return dp_root;}int rob(TreeNode* root) {pair<int,int> ans=TreeDp(root);return max(ans.first,ans.second);}
};

2.ACWing:285. 没有上司的舞会

模板题
285. 没有上司的舞会
在这里插入图片描述
  这题和 打家劫舍 III 基本上一摸一样呀,只不过这里可能是图,并且acwing需要自己构建结点,因此这里可以提供更多建树dp思路。

没有职员愿意和直接上司一起参会。实际上就是指相邻父子结点不能同时被选择。

1.1、定义状态转移方程

  这里我们的状态和打家劫舍III的状态一模一样。不过我们可以使用拓扑排序的方式实现,只需要存储fa数组,表示父节点,因为根结点的状态由子节点转移而来,并且没必要按顺序转移~。用邻接表存图,然后按照打家劫舍III dfs实现会简单很多。

1.2、拓扑排序参考代码
#include<bits/stdc++.h>
using namespace  std;
int main(void){ios_base::sync_with_stdio(false);cin.tie(0);int N;cin>>N;vector<int> fa(N+1,-1);//拓扑排序方法 找寻前驱用vector<int> degree(N+1,0);//用于拓扑排序删除度的数组vector<pair<int,int>> dp(N+1);//dp状态数组,second表示被选择,first表示不被选择。for(int i=1;i<=N;++i) {cin>>dp[i].second;dp[i].first=0;}for(int i=1;i<N;++i){int son,Fa;cin>>son>>Fa;fa[son]=Fa;degree[Fa]++;}//为了和打家劫舍不一样,我们这里采用拓扑排序的方式//如果仍然用dfs,则需要找到根结点,dfs会方便很多,也不需要fa数组了stack<int> sta;for(int i=1;i<=N;++i){if(degree[i]==0){sta.push(i);}}int ans=0;while(!sta.empty()){int cur=sta.top();sta.pop();if(fa[cur]!=-1) {dp[fa[cur]].first += max(dp[cur].first, dp[cur].second);dp[fa[cur]].second += dp[cur].first;if (--degree[fa[cur]] == 0) sta.push(fa[cur]);}elseans=max(ans,max(dp[cur].first,dp[cur].second));}cout<<ans;return 0;
}
1.3、dfs后序遍历参考代码
#include<bits/stdc++.h>
using namespace  std;
vector<vector<int> > g;
vector<pair<int,int>> dp;//dp状态数组,second表示被选择,first表示不被选择。
void dfs(int root){for(int i=0;i<g[root].size();++i){dfs(g[root][i]);dp[root].second+=dp[g[root][i]].first;dp[root].first+=max(dp[g[root][i]].first,dp[g[root][i]].second);}return;
}
int main(void){ios_base::sync_with_stdio(false);cin.tie(0);int N;cin>>N;g.assign(N+1,vector<int>{});dp.assign(N+1,{});//dp状态数组,second表示被选择,first表示不被选择。unordered_set<int> roots;//用于保存根for(int i=1;i<=N;++i) {cin>>dp[i].second;dp[i].first=0;roots.insert(i);}for(int i=1;i<N;++i){int son,Fa;cin>>son>>Fa;g[Fa].emplace_back(son);roots.erase(son);}int ans=0;for(auto & i:roots){dfs(i);ans+=max(dp[i].first,dp[i].second);//从不同根遍历}cout<<ans;return 0;
}

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

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

相关文章

【算法刷题 | 二叉树 06】4.10( 路径总和、路径总和 || )

文章目录 13.路径总和13.1问题13.2解法一&#xff1a;递归13.2.1递归思路&#xff08;1&#xff09;确定递归函数参数以及返回值&#xff08;2&#xff09;确定终止条件&#xff08;3&#xff09;确定递归逻辑 13.2.2代码实现 14.路径总和 ||14.1问题14.2解法一&#xff1a;递归…

第四百四十二回 再谈flutter_launcher_icons包

文章目录 1. 概念介绍2. 使用方法3. 示例代码4. 经验与总结4.1 经验分享4.2 内容总结 我们在上一章回中介绍了"overlay_tooltip简介"相关的内容&#xff0c;本章回中将 再谈flutter_launcher_icons包.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1. 概念介绍 …

Redux和Redux Toolkit

Redux 概念&#xff1a;redux是react最常用的集中状态管理工具&#xff0c;类似于Vue中的Pinia(vuex)&#xff0c;可以独立于框架运行作用&#xff1a;通过集中管理的方式管理应用的状态 Redux快速体验 不和任何框架绑定&#xff0c;不使用任何构建工具&#xff0c;使用纯Re…

2024年面试AI编译器岗经验总结

面试经历: 面试中必备的知识: 1.用C++实现一个卷积 (图解)一步一步使用CPP实现深度学习中的卷积 - GiantPandaCVGiantPandaCVhttp://giantpandacv.com/academic/%E7%AE%97%E6%B3%95%E7%A7%91%E6%99%AE/%E5%B0%BD%E8%A7%88%E5%8D%B7%E7%A7%AF%E7%A5%9E%E7%BB%8F%E7%BD%91%E…

支小蜜校园刷脸支付系统的优势在哪里?

在当今社会&#xff0c;校园欺凌问题日益受到人们的关注。校园欺凌不仅影响学生的身心健康&#xff0c;还可能导致其产生厌学、逃学甚至报复社会的行为。建立校园防欺凌系统对于学校而言&#xff0c;具有极其重要的意义。本文将详细探讨校园防欺凌系统对学校的好处。 一、保障…

Harmony鸿蒙南向驱动开发-Regulator

Regulator模块用于控制系统中各类设备的电压/电流供应。在嵌入式系统&#xff08;尤其是手机&#xff09;中&#xff0c;控制耗电量很重要&#xff0c;直接影响到电池的续航时间。所以&#xff0c;如果系统中某一个模块暂时不需要使用&#xff0c;就可以通过Regulator关闭其电源…

学习笔记:解决拖延

1 解决拖延、减轻压力的关键心态和方法 1.1 要点梳理 拖延是因为自己一直在逃避&#xff0c;重点是要有效突破逃避圈&#xff0c;进入学习圈&#xff0c;扩展成长圈。 毒蛇曲线&#xff08;见思维导图&#xff09;中越是临近截止期限&#xff0c;拖延的焦虑越上升&#xff0…

springcloud第4季 使用resilience4j实现服务流量治理

一 前言 1.1 断路器介绍 断路器是一种开关装置&#xff0c;当某个服务单元发生故障后&#xff0c;通过断路器向调用方返回一个符合预期&#xff0c;可处理的备选响应。保证服务不会被长时间&#xff0c;不必要的占用&#xff0c;从而避免在分布式系统故障的蔓延、乃至雪崩。…

onSaveInstanceState()与onRestoreInstanceState()

目录 1.二者作用 2.onSaveInstanceState调用时机 2.1 五种情况 前4种情况Activity生命周期&#xff1a; 2.2 注意事项&#xff1a;确定会被系统回收并销毁&#xff0c;不会调用此方法 两个例子 3.onRestoreInstanceState调用时机 3.1实例——屏幕切换生命周期 3.2 极端…

python爬虫 爬取网页图片

http://t.csdnimg.cn/iQgHw //爬虫爬取图片其实是很简单的&#xff0c;但是大多数同学&#xff0c;可能对 url的设置一直有困惑&#xff08;这点本人也在研究&#xff09;&#xff0c;而本篇文章&#xff0c;对于想要爬取图片的小白简直是福利。你只需要将文章代码运行即可&am…

三种常见webshell工具的流量特征分析

又来跟师傅们分享小技巧了&#xff0c;这次简单介绍一下三种常见的webshell流量分析&#xff0c;希望能对参加HW蓝队的师傅们有所帮助。 什么是webshell webshell就是以asp、php、jsp或者cgi等网页文件形式存在的一种代码执行环境&#xff0c;主要用于网站管理、服务器管理、…

Kotlin:常用标准库函数(let、run、with、apply、also)

一、let 扩展函数 Kotlin标准库函数let可用于范围确定和空检查。当调用对象时&#xff0c;let执行给定的代码块并返回其最后一个表达式的结果。对象可以通过引用(默认情况下)或自定义名称在块中访问。 let扩展函数源码 let.kt文件代码 fun main() {println("isEmpty $is…

处理慢查询时使用explain一般看哪些字段

explain之后会出现这些&#xff0c;一般就只看下面这几个字段 select_type就是查询类型&#xff0c;在我司的业务里基本上用的都是简单查询&#xff0c;在内存中处理逻辑&#xff0c;复杂查询的话排查问题比较麻烦&#xff0c;引起慢查询还会拖累数据库&#xff0c;数据库里还…

c#获取Web.Config中的值出现的错误及解决办法

c#获取Web.Config中的值出现的错误及解决办法 1.错误提示 2.原因寻找 问题出在Web.Config文件中 <add key"mchid " value"1495103432"/>//mchid 后面不应该有空格图示如下&#xff1a; 3.改正代码如下&#xff1a; <?xml version"1.0…

【Linux-运维】查看操作系统的指定端口占用情况确定端口是哪个服务占用

不同的查看端口占用的方法&#xff0c;应用场景有所不同 一、查询某个端口是否被占用&#xff1f;lsof -i:端口号lsof -i:协议 查看某个协议的占用情况netstat -tlnp|grep 端口号ss -tlnp|grep 端口号fuser 端口号/协议ls -l /proc/$(lsof -t -i:端口号)|grep exe 二、确认指定…

OpenC910 datasheet 2.0 翻译

概述 C910是由THEAD半导体有限公司开发的一款RISC-V兼容的64位高性能处理器。它通过架构和微架构创新&#xff0c;在控制流、计算和频率方面提供行业领先的性能。C910处理器基于RV64GC指令集&#xff0c;并实现了XIE&#xff08;XuanTie指令扩展&#xff09;技术。C910采用先进…

2024-04-10 作业

作业要求&#xff1a; 1> 思维导图 2> 作业1&#xff1a; 作业2&#xff1a; 运行代码&#xff1a; main.cpp #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QDebug> #include <QTimerEvent> #include <QTime> #include &…

STC89C52学习笔记(四)

STC89C52学习笔记&#xff08;四&#xff09; 综述&#xff1a;本文讲述了在STC89C51中数码管、模块化编程、LCD1602的使用。 一、数码管 1.数码管显示原理 位选&#xff1a;对74HC138芯片的输入端的配置&#xff08;P22、P23、P24&#xff09;&#xff0c;来选择实现位选&…

大话设计模式——17.状态模式(State Pattern)

简介 对象的行为依赖于它的状态&#xff08;属性&#xff09;&#xff0c;可以根据状态的改变而改变相关行为。 UML图&#xff1a; 应用场景&#xff1a; 对象的行为取决于其状态&#xff0c;并且必须要在运行时刻根据状态而改变行为代码中包含大量与对象状态有关的条件语句 …

嵌入式Linux:Linux库函数

目录 1、Linux库函数简介 2、标准C语言库函数 1、Linux库函数简介 Linux 提供了丰富的库函数&#xff0c;涵盖了各种领域&#xff0c;从文件操作到网络编程、图形界面、数学运算等。这些库函数大多数都是标准的 C 库函数&#xff0c;同时也包括一些特定于 Linux 系统的库。 …