动态规划之树形DP

news2024/9/23 17:13:16

动态规划之树形DP

  • 树形DP
    • 何为树形DP
  • 树形DP例题
    • HDU-1520 Anniversary party
    • HDU-2196 Computer
    • 834. 树中距离之和

树形DP

何为树形DP

树形DP是指在“树”这种数据结构上进行的动态规划:给出一颗树,要求以最少的代价(或取得最大收益)完成给定的操作。通常这类问题规模比较大,枚举算法效率低,无法胜任,贪心算法不能求得最优解,因此需要用动态规划进行求解。

在树上做动态规划显得非常合适,因为树本身有“子结构”性质(树和子树),具有递归性,符合DP性质。相比线性DP,树形DP的状态转移方程更加直观。

树形动态规划(Tree DP)是一种动态规划算法,在处理树状结构(例如树、森林、有向无环图等)上的问题时非常常见和有效。树形动态规划通过将问题拆解为子问题,并利用子问题的解来求解更大规模的问题。

在树形动态规划中,我们需要定义一个适合的状态和状态转移方程。一般来说,状态可以定义为以当前节点为根的子树的某种性质,例如最大路径和、最长路径长度、最大权值和等等。而状态转移方程则描述了如何由子节点的状态计算当前节点的状态。

树形动态规划的典型做法是使用深度优先搜索(DFS)遍历整个树,在遍历过程中进行状态的计算和更新。通过递归地计算子节点的状态,并将其传递给父节点,可以得到整个树的最终状态。

在实现树形动态规划时,需要注意避免重复计算,可以使用记忆化搜索或者自底向上的方式进行计算。此外,还要注意选择合适的遍历顺序,以保证子问题的状态在计算当前节点状态时已经求解完毕。

总而言之,树形动态规划是一种针对树状结构问题的动态规划算法,通过拆解问题为子问题,并利用子问题的解求解更大规模的问题。它在解决树相关的问题时具有重要的应用价值。

树形DP例题

HDU-1520 Anniversary party

HDU-1520 Anniversary party

image-20230724114827116

题目大意

邀请员工参加party,但是为了避免员工和直属上司发生尴尬,规定员工和直属上司不能同时出席。

也就是每个人代表树中一个结点,每个结点拥有一个权值,相邻的父结点和子结点只能选择一个,问如何取才能使总权值之和最大。

员工编号从1到N。第一行输入包含数字N。1 < = N < = 6000。随后的N行中的每一行都包含相应员工的愉快度评级。欢乐评级是一个介于-128到127之间的整数。然后是描述主管关系树的T行。树规范的每一行都具有以下形式: L K 这意味着第K个员工是第L个员工的直接主管。输入以一行结束 0 0

解题思路

根据DP的解题思路,定义状态为:

d p [ i ] [ 0 ] dp[i][0] dp[i][0],表示不选择当前结点时候的最优解

d p [ i ] [ 1 ] dp[i][1] dp[i][1],表示选择当前结点时候的最优解

其中状态转移方程分为下面两种情况:

  1. 不选择当前结点,则子结点可选可不选,取其中的最大值即可,也就是 d p [ u ] [ 0 ] + = m a x ( d p [ s o n ] [ 0 ] , d p [ s o n ] [ 1 ] ) dp[u][0] += max(dp[son][0], dp[son][1]) dp[u][0]+=max(dp[son][0],dp[son][1])
  2. 选择当前结点,则其子结点不能选, d p [ u ] [ 1 ] + = d p [ s o n ] [ 0 ] dp[u][1] += dp[son][0] dp[u][1]+=dp[son][0]

AC代码

#include<bits/stdc++.h>
using namespace std;
const int maxn = 6010;
vector<int>tree[maxn];
int dp[maxn][2], father[maxn], value[maxn];
void dfs(int u) {
    dp[u][0] = 0; // 不参加party
    dp[u][1] = value[u]; // 参加party
    for(int i = 0; i < tree[u].size(); i++) {
        int son = tree[u][i];
        dfs(son); // 深搜子结点
        dp[u][0] += max(dp[son][0], dp[son][1]); // 父结点不选,子结点可选可不选
        dp[u][1] += dp[son][0]; // 父结点选择,子结点不能选
    }
}
int main() {
    int n, a, b;
    while(~scanf("%d", &n)) {
        for(int i = 1; i <= n; i++) {
            scanf("%d", &value[i]);
            tree[i].clear();
            father[i] = -1;
        }
        memset(dp, 0, sizeof(dp));
        while(true) { // 建树
            scanf("%d%d", &a, &b);
            if(a == 0 && b == 0) break;
            tree[b].push_back(a);
            father[a] = b; // 父子关系,表示a的父亲是b
        }
        for(int i = 1; i <= n; i++) {
            if(father[i] == -1) { // 寻找父结点,从父结点开始深搜
                dfs(i);
                printf("%d\n", max(dp[i][0], dp[i][1]));
                break;
            }
        }
    }
    return 0;
}

HDU-2196 Computer

HDU-2196 Computer

image-20230729105959490

题目大意

一颗树,根节点的编号是1,对其中的任意一个结点,求离这个结点最远结点的距离。

输入包含多个测试用例,每个用例的第一行是一个自然数N,N不超过10000,接下来N-1行,每行输入两个整数:第一个整数为某结点id,第二个整数为连接到这个结点需要的距离。

输出N行,表示距离第i个结点的最远距离

解题思路

对于求解距离某结点的最长距离,应当分为两种情况:

对于任意一个结点

  1. 结点 i i i 的子树中的结点距离结点 i i i 的最长距离,而为了方便第二种情况的计算,第一次深搜的时候需要记录结点 i i i 的子树中某个结点到该结点的最长距离和第二长的距离。

  2. 第二种情况就是结点 i i i 往上走,而此时往上走又分为两种情况:

    从结点 i i i 往上继续走,没有走结点 i i i 的父结点到其子树的最长距离的路径

    若结点 i i i 沿着其父结点的最长路径上走时,则需考虑结点 i i i 是否在其父结点的最长子树上,此时则需要用到最初求得各个结点到其它结点的最长距离和次长距离,如果结点 i i i 在其父结点的最长子树上那么 X = two + dist(i, i 的父结点),否则 X = one + dist(i, i 的父结点)

综上所述,第一种情况和第二种情况求得两个值的最大值即为答案

状态的设计:结点 i i i 的子树到 i i i 的最长距离 d p [ i ] [ 0 ] dp[i][0] dp[i][0] 以及次长距离 d p [ i ] [ 1 ] dp[i][1] dp[i][1],从结点 i i i 往上走的最长距离 d p [ i ] [ 2 ] dp[i][2] dp[i][2]

#include<bits/stdc++.h>
using namespace std;
struct Node {
    int id; // 子树结点编号
    int cost;
};
const int maxn = 10010;
int dp[maxn][3], n;
vector <Node> tree[maxn];
void init() { // 初始化和建树
    for(int i = 1; i <= n; i++) tree[i].clear();
    memset(dp, 0, sizeof(dp));
    int x, y;
    for(int i = 2; i <= n; i++) {
        scanf("%d%d", &x, &y);
        Node tmp;
        tmp.id = i;
        tmp.cost = y;
        tree[x].push_back(tmp);
    }
}
void dfs1(int father) { // 搜索以结点father为起点到子树的最长距离和次长距离
    int one = 0, two = 0; // one 记录father往下走的最长距离,two记录次长距离
    for(int i = 0; i < tree[father].size(); i++) { // 先处理子结点再处理父结点
        Node child = tree[father][i];
        dfs1(child.id); // 递归子结点,直到最底层
        int temp = child.cost + dp[child.id][0];
        if(temp >= one) {
            two = one;
            one = temp;
        }
        if(temp < one && temp > two) two = temp;
    }
    dp[father][0] = one; // 得到以father为起点的子树的最长距离
    dp[father][1] = two; // 得到以father为起点的子树的次长距离
}
void dfs2(int father) { // 先处理父结点再处理子结点
    for(int i = 0; i < tree[father].size(); i++) {
        Node child = tree[father][i];
        if(child.cost + dp[child.id][0] == dp[father][0]) // child在最长距离的子树上
            dp[child.id][2] = max(dp[father][1], dp[father][2]) + child.cost;
        else dp[child.id][2] = max(dp[father][0], dp[father][2]) + child.cost; //否则
        dfs2(child.id);
    }
}
int main() {
    while(~scanf("%d", &n)) {
        init();
        dfs1(1);
        dp[1][2] = 0;
        dfs2(1);
        for(int i = 1; i <= n; i++) {
            printf("%d\n", max(dp[i][0], dp[i][2]));
        }
    }
    return 0;
}

834. 树中距离之和

834. 树中距离之和

给定一个无向、连通的树。树中有 n 个标记为 0...n-1 的节点以及 n-1 条边 。

给定整数 n 和数组 edgesedges[i] = [ai, bi]表示树中的节点 aibi 之间有一条边。

返回长度为 n 的数组 answer ,其中 answer[i] 是树中第 i 个节点与所有其他节点之间的距离之和。

示例 1:

img

输入: n = 6, edges = [[0,1],[0,2],[2,3],[2,4],[2,5]]
输出: [8,12,6,10,10,10]
解释: 树如图所示。
我们可以计算出 dist(0,1) + dist(0,2) + dist(0,3) + dist(0,4) + dist(0,5) 
也就是 1 + 1 + 2 + 2 + 2 = 8。 因此,answer[0] = 8,以此类推。

参考灵神的题解写出来的,不得不说灵神 yyds,比官方给出的题解简单多了。

灵神的题解地址:传送门

灵神给出的解题思路如下图所示:

在这里插入图片描述

AC代码:

class Solution {
public:
    static const int maxn = 3e4 + 10;
    vector<int>tree[maxn];
    vector<int> sumOfDistancesInTree(int n, vector<vector<int>>& edges) {
        for(auto edge : edges) {
            tree[edge[0]].push_back(edge[1]);
            tree[edge[1]].push_back(edge[0]);
        }
        vector<int>ans(n);
        vector<int>children(n, 1); // 每颗子树所包含结点数量
        function<void(int, int, int)> dfs1 = [&](int child, int father, int depth) {
            ans[0] += depth;
            for(auto i : tree[child]) {
                if(i != father) { // 避免访问父结点
                    dfs1(i, child, depth + 1);
                    children[child] += children[i]; // 累加子树大小
                }
            }
        };
        dfs1(0, -1, 0); // 0 没有父结点
        function<void(int, int)> dfs = [&](int child, int father) {
            for(auto i : tree[child]) {
                if(i != father) {
                    ans[i] += (ans[child] + n - children[i] * 2);
                    dfs(i, child);
                }
            }
        };
        dfs(0, -1);
        return ans;
    }
};

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

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

相关文章

Vue-前端工程化

前后端开发模式 早期开发是前后端混合开发 即前后端代码写在一个工程中 前端写完给后端&#xff0c;后端发现问题反映给前端&#xff0c;后端就需要懂全栈 YAPI&#xff08;接口在线管理平台&#xff09; 一个接口管理和编辑平台 现在已经没有在维护了 还有mock服务 就是根据…

leetcode 912.排序数组

⭐️ 题目描述 &#x1f31f; leetcode链接&#xff1a;排序数组 思路&#xff1a; 此题如果使用冒泡插入选择这些时间复杂度 O ( N 2 ) O(N^2) O(N2) 的算法会超时&#xff0c;使用快排 优化也过不去&#xff0c;因为里面有一个测试用例全是 2 即使加了三数取中也会是 O (…

Java I/O模型

目录 一、介绍I/O 二、Java中常见的I/O 1.BIO&#xff0c;同步阻塞IO 2.NIO&#xff0c;同步非阻塞IO 3.AIO&#xff0c;异步I/O 三、三种IO的区别 一、介绍I/O 1.I/O&#xff08;Input/Output&#xff09;的即为输入输出。 2.从计算机的角度来看&#xff1a;在冯诺伊曼…

Vector - CAPL - 诊断模块函数(连接配置)

CanTpGetMaximumReceiveLength & CanTpSetMaximumReceiveLength 功能&#xff1a;获取或设置该节点接收数据时可接受的最大长度&#xff1b;如果指示较长数据块的第一帧到达&#xff0c;则溢出 (FC.Ovflw) 流状态流控制帧将被发送回发送器。 注意&#xff1a;CANoe 支持高…

NOsql之MongoDB入门分享

目录 一、MongoDB简介 1、概念理解 2、yum安装部署 3、二进制安装部署 4、配置文件解析 二、MongoDB基本管理 1、登录操作 2、管理命令 3、用户管理 一、MongoDB简介 1、概念理解 关系型数据库&#xff08;RDBMS:Relational Database Management System) MySql、Ora…

YOLO实战1.2-YOLOV5人脸检测

上个项目实现了车牌的检测和识别&#xff0c;这次实现一个简单的人脸检测(加口罩)项目 一.数据集 使用1200张图片进行训练 400张进行测试 二.各项指标 三.效果

SpringCloud Gateway 在微服务架构下的最佳实践

作者&#xff1a;徐靖峰&#xff08;岛风&#xff09; 前言 本文整理自云原生技术实践营广州站 Meetup 的分享&#xff0c;其中的经验来自于我们团队开发的阿里云 CSB 2.0 这款产品&#xff0c;其基于开源 SpringCloud Gateway 开发&#xff0c;在完全兼容开源用法的前提下&a…

云服务器开放端口

文章目录 云服务器开放端口1. 云服务器后台开放端口2. linux系统开放端口2.1 查看防火墙状态2.2 开启防火墙2.3 开放端口&#xff08;以端口2000为例&#xff09;2.4 重新载入防火墙2.5 查看已开放的端口2.6 重启防火墙2.7 关闭端口 云服务器开放端口 本篇文章主要以阿里云服务…

Pandas.rank() 函数详解

文章目录 Pandas.rank() 函数详解一、参数解析二、案例分享默认排名降序: ascending Falsemethod minmethod maxmethod firstmethod densena_optionbottompct True Pandas.rank() 函数详解 一、参数解析 method&#xff1a;指定排名时的策略。 默认值为 average&#x…

[python]conda激活环境后pip -V显示在base路径

正常激活环境后pip -V是在激活环境路径&#xff0c;比如 但是我的突然确为 D:\anaconda3\lib\site-packages里面&#xff0c;百思不得其解&#xff0c;后来发现是我在环境变量加了PYTHONHOME环境变量&#xff0c;如果你的环境变量有PYTHONHOME或者PYTHONPATH请删掉试试。 此外…

Java并发系列之二:悲观锁机制

什么是锁 在并发环境下&#xff0c;会出现多个线程对同一个资源进行争抢的情况&#xff0c;假设A线程对资源正在进行修改&#xff0c;此时B线程此时又对资源进行了修改&#xff0c;这就可能会导致数据不一致的问题。为了解决这个问题&#xff0c;很多编程语言引入了锁机制&…

Stephen Wolfram:机器学习与神经网络训练

Machine Learning, and the Training of Neural Nets 机器学习与神经网络训练 We’ve been talking so far about neural nets that “already know” how to do particular tasks. But what makes neural nets so useful (presumably also in brains) is that not only can t…

【前端知识】React 基础巩固(四十六)——自定义Hook的应用

React 基础巩固(四十六)——自定义Hook的应用 一、自定义Hook的应用 自定义Hook本质上只是一种函数代码逻辑的抽取&#xff0c;严格意义上而言&#xff0c;它并不算React的特性。 实现组件创建/销毁时打印日志 import React, { memo, useEffect, useState } from "react…

#P0994. [NOIP2004普及组] 花生采摘

题目描述 鲁宾逊先生有一只宠物猴&#xff0c;名叫多多。这天&#xff0c;他们两个正沿着乡间小路散步&#xff0c;突然发现路边的告示牌上贴着一张小小的纸条&#xff1a;“欢迎免费品尝我种的花生&#xff01;――熊字”。 鲁宾逊先生和多多都很开心&#xff0c;因为花生正…

java学习路程之篇五、进阶知识、常用API、Object类、Math类、System类、BigDecimal类、包装类

文章目录 1、介绍2、Object类3、Math类4、System类5、BigDecimal类6、包装类 1、介绍 2、Object类 3、Math类 4、System类 5、BigDecimal类 6、包装类

华为云低代码平台Astro Canvas 搭建汽车展示大屏——实验指导手册

实验背景 大屏应用Astro Canvas是华为云低代码平台Astro的子服务之一&#xff0c;是以数据可视化为核心&#xff0c;以屏幕轻松编排&#xff0c;多屏适配可视为基础&#xff0c;用户可通过图形化界面轻松搭建专业水准的数据可视化大屏。例如汽车展示大屏、监控大屏、项目开发大…

【Docker】Docker安装Consul

文章目录 1. 什么是Consul2. Docker安装启动Consul 点击跳转&#xff1a;Docker安装MySQL、Redis、RabbitMQ、Elasticsearch、Nacos等常见服务全套&#xff08;质量有保证&#xff0c;内容详情&#xff09; 1. 什么是Consul Consul是HashiCorp公司推出的开源软件&#xff0c;提…

JVM面试题--类加载器

什么是类加载器&#xff0c;类加载器有哪些 类加载子系统&#xff0c;当java源代码编译为class文件之后&#xff0c;由他将字节码装载到运行时数据区 BootStrap ClassLoader 启动类加载器或者叫做引导类加载器&#xff0c;是用c实现的&#xff0c;嵌套在jvm内部&#xff0c;…

站群站点日志优化

需求&#xff1a; 1&#xff0c;每个站点的日志需要记录到请求的域名 2&#xff0c;日志需要自动切割保存前三天的内容&#xff0c;防止日志无限增长 3&#xff0c;日志要有利于二次分析 实现&#xff1a; 1&#xff0c;nginx需要修改两个地方 nginx配置 时间 …

JavaScript(二)函数及对象

函数 函数体中return后面不能再添加任何代码&#xff0c;因为不会执行 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0&quo…