【树形 DP】如何从“方向“角度理解树形 DP

2023-09-22 11:03:22

题目描述

这是 LeetCode 上的 834. 树中距离之和 ,难度为 「困难」

Tag : 「树形 DP」、「DFS」、「动态规划」、「树」

给定一个无向、连通的树。

树中有 n 个标记为 0...n-1 的节点以及 n-1 条边 。

给定整数 n 和数组 edges 表示树中的节点 之间有一条边。

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

示例 1: alt

输入: 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,以此类推。

示例 2: alt

输入: n = 1, edges = []

输出: [0]

示例 3: alt

输入: n = 2, edges = [[1,0]]

输出: [1,1]

提示:

  • 给定的输入保证为有效的树

树形 DP

对于树形 DP,可以随便以某个节点为根,把整棵树“拎起来”进行分析,通常还会以“方向”作为切入点进行思考。

不妨以编号为 0 的节点作为根节点进行分析:假设当前处理到的节点为 u,当前节点 u 的父节点为 fa,同时 u 有若干子节点 j

alt

对于任意节点 u 而言,其树中距离之和可根据「方向/位置」分为两大类(对应示例图的左右两部分):

  • 所有从节点 u “往下”延伸所达的节点距离之和,即所有经过 u -> j 边所能访问到的节点距离之和
  • 所有从节点 u “往上”延伸所达的节点距离之和,即经过 u -> fa 边所能访问到的节点距离之和
alt

假设我们能够用 预处理出每个节点“往下”和“往上”的距离之和,那么就有

不失一般性分别考虑 该如何计算。

为了方便,起始先用「链式前向星」对 edges 进行转存,同时在递归计算 时,将父节点 fa 也进行传递,从而避免遍历节点 u 的出边时,重新走回 fa

的推导

对于叶子节点(没有“往下”出边的节点),我们有 的天然条件,计算好的叶子节点值可用于更新其父节点,因此 求解 是一个「从下往上」的递推过程

假设当前处理到的节点是 u,往下节点有 ,且所有 均已计算完成。

由于 是由所有存在“往下”出边的节点 j 贡献而来。而单个子节点 j 来说,其对 的贡献应当是:在所有原有节点到节点 j 的距离路径中,额外增加一条当前出边(u -> j),再加上 1(代表节点 u 到节点 j 的距离)

原路径距离之和恰好是 ,额外需要增加的出边数量为原来参与计算 的点的数量(即挂载在节点 j 下的数量),因此我们还需要一个 c 数组,来记录某个节点下的子节点数量。

最终的 为所有符合条件的节点的 j 的总和。

的推导

对于树形 DP 题目,“往下”的计算往往是容易的,而“往上”的计算则是稍稍麻烦。

假设当前我们处理到节点为 u,将要遍历的节点为 j,考虑如何使用已经计算好的 来求解

这里为什么是求解 ,而不是 呢?

因为我们求解的方向是“往上”的部分,必然是用父节点的计算结果,来推导子节点的结果,即 求解 是一个「从上往下」的过程

对于树形 DP ,通常需要对“往上”进一步拆分:「往上再往上」和「往上再往下」:

  • 往上再往上:是指经过了 j -> u 后,还必然经过 u -> fa 这条边时,所能到达的节点距离之和:

    alt

    这部分对 的贡献为:在所有原有节点到节点 u 的距离路径中,额外增加一条当前出边(u -> j),增加当前出边的数量与节点数量相同,点数量为 ,含义为 总节点数量 减去 u 节点以及子节点数量。

    即此部分对 的贡献为

  • 往上再往下:是指经过了 j -> u 后,还经过「除 u -> j 以外」的其他“往下”边时,所能到达的节点距离之和:

    alt

    这部分的计算需要先在 中剔除 的贡献,然后再加上额外边(u -> j)的累加数量,同样也是节点数量。

    中剔除 后为 ,而点的数量为 ,含义为在以节点 u 为根的子树中剔除调用以节点 j 为根节点的部分。

    即此部分对 的贡献为

Java 代码:

class Solution {
    int N = 30010, M = 60010, idx = 0, n;
    int[] he = new int[N], e = new int[M], ne = new int[M];
    int[] f = new int[N], c = new int[N], g = new int[N];
    void add(int a, int b) {
        e[idx] = b;
        ne[idx] = he[a];
        he[a] = idx++;
    }
    public int[] sumOfDistancesInTree(int _n, int[][] es) {
        n = _n;
        Arrays.fill(he, -1);
        for (int[] e : es) {
            int a = e[0], b = e[1];
            add(a, b); add(b, a);
        }
        dfs1(0, -1);
        dfs2(0, -1);
        int[] ans = new int[n];
        for (int i = 0; i < n; i++) ans[i] = f[i] + g[i];
        return ans;
    }
    int[] dfs1(int u, int fa) {
        int tot = 0, cnt = 0;
        for (int i = he[u]; i != -1; i = ne[i]) {
            int j = e[i];
            if (j == fa) continue;
            int[] next = dfs1(j, u);
            tot += next[0] + next[1] + 1; cnt += next[1] + 1;
        }
        f[u] = tot; c[u] = cnt;
        return new int[]{tot, cnt};
    }
    void dfs2(int u, int fa) {
        for (int i = he[u]; i != -1; i = ne[i]) {
            int j = e[i];
            if (j == fa) continue;
            g[j] += g[u] + n - 1 - c[u]; // 往上再往上
            g[j] += f[u] - f[j] - c[j] + c[u] - 1 - c[j];  // 往上再往下
            dfs2(j, u);
        }
    }
}
  • 时间复杂度:
  • 空间复杂度:

最后

这是我们「刷穿 LeetCode」系列文章的第 No.834 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。

在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。

为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode 。

在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。

更多更全更热门的「笔试/面试」相关资料可访问排版精美的 合集新基地 🎉🎉

本文由 mdnice 多平台发布

更多推荐

清易低功耗智能雨量监测站概述

一、低功耗智能雨量监测站概述产品概述低功耗智能雨量监测站基于智能传感、无线通信、智能处理与智能控制等物联网技术的开发,利用智能传感技术,通过传感器测量降雨量,并使用物联网进行传输。无需专门的通信线路,在联网的状态下,数据可快速、主动的上报到云平台,用户可在电脑或手机,随时随地浏览数据。二、技术参数测量参数降雨量◇测量范

Faunadb

Faunadb和googlespanner都属于云分布式数据库天然支持分片(无需做分表分库操作,一库搞定,当然价格另说),国内的也有比如TiDBOceanbase等本文使用java语言,其他语言可以跳过;有想直接使用的可以参考(无法访问外网,可以搞个vpn吧!!!,有时会遇到网络问题):GitHub-fauna/fau

WebGIS开发教程:Cesium里面的Entity和primitive有什么区别

EntityEntity是Cesium中最重要的概念之⼀,它通常用于描述具有坐标位置的实际对象,例如⻜机、汽⻋、楼房、⼈物等。每个Entity实例都有不同的属性,例如位置、姿态、缩放、颜⾊、贴图等,并且可以通过编程⽅式创建、修改、删除。Entity的优点是⾮常灵活和易于使用。由于Entity是更⾼层次的概念,因此它可以

Vue中如何进行跨域处理

Vue中的跨域请求处理:解决前端开发中的常见问题跨域请求是前端开发中常见的问题之一。Vue.js是一款流行的前端框架,如何在Vue中处理跨域请求是每个Vue开发者都需要了解的重要课题。本文将深入探讨什么是跨域请求,为什么它会出现,以及如何在Vue中处理跨域请求,包括使用代理、JSONP、CORS等方法。什么是跨域请求?

开源网安入选广东省网络空间安全标准化技术委员会新技术及应用安全技术工作组成员单位

近日,第二届广东省网络空间安全标准化技术委员会(GD/TC124)(以下简称省网安标委)正式成立。为进一步发挥省网安标委在支撑网络强国建设、推进网络安全产业高质量发展过程中,示范引领核心技术攻关、创新产品研发、行业应用推广的重要作用,由省网安标委秘书处拟牵头组建数据安全技术工作组、网络安全技术工作组、新技术及应用安全技

GDPU 数据结构 天码行空3

一、【实验目的】1、掌握建立单链表的基本方法。2、掌握单链表的插入、删除算法的思想和实现二、【实验内容】仿照教材中的单链表实现例子,自己设计一个有序单链表,单链表中的数据元素为整型并递增有序。有序单链表的定义:逻辑结构:有序线性表,数据元素递增有序存储结构:链式操作集合:初始化、插入、删除、撤销(1)ListIniti

JavaWeb 学习笔记 6:会话跟踪

JavaWeb学习笔记6:会话跟踪HTTP协议本身是无状态的,所以不能跟踪会话状态。所以会有额外的技术用于跟踪会话:Cookie,客户端技术Session,服务端技术1.Cookie1.1.写入Cookie可以在服务端通过HttpServletResponse.addCookie向浏览器写入Cookie:@WebSer

C++11之基础篇

C++11C++11简介统一的列表初始化{}初始化std::initializer_list声明autodecltypenullptr范围for循环STL中一些变化arrayforward_listunderored_map,underored_setC++11简介在2003年C++标准委员会曾经提交了一份技术勘误表(

Vue的`provide`和`inject`特性:上下文传递与数据共享

Vue的provide和inject特性:上下文传递与数据共享Vue.js是一款流行的前端JavaScript框架,它提供了丰富的功能来构建可维护和可扩展的用户界面。其中,provide和inject特性是Vue中的一项强大功能,它们允许你在父组件提供数据,并在子组件中进行注入,实现了上下文传递和数据共享的目的。本文将

RockTree TOKEN2049 Party爆火,一场千亿规模的“超级聚会”

今年9月11日至17日期间,在新加坡举办的TOKEN2049大会,成为了今年同类活动中规模最大、最火爆的一次Web3行业盛会。据悉,本届TOKEN2049迎来了来自3,500多个组织超10,000名与会者,并有一众重磅加密行业嘉宾出席会议。而在TOKEN2049大会举办期间的系列活动中,由RockTreeCapital

腾讯云16核CPU服务器配置大全,CVM和轻量服务器

腾讯云16核CPU服务器有哪些配置可以选择?可以选择标准型S6、标准型SA3、计算型C6或标准型S5等,目前标准型S5云服务器有优惠活动,性价比高,计算型C6云服务器16核性能更高,轻量16核32G28M带宽优惠价3468元15个月,腾讯云百科分享腾讯云16核CPU服务器可以选择的云服务器CVM规格列表:目录腾讯云16

热文推荐