AcWing 1073. 树的中心(树形dp)

本文介绍了一种算法,通过深度优先遍历解决树中找到离其他节点最远但距离最近的点的问题。涉及向下和向上路径分析,利用dp求解最短路径,并处理负权边情况。核心在于递归计算节点间的最大距离并取最小值。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在这里插入图片描述
在这里插入图片描述

题意:

给定一棵树,树中包 含 n 个结点(编号1~n)和 n−1无向边每条边都有一个正权值

在树中找到一个点,使得 该点到树中其他结点的最远距离最近

思路:

我们以题目给出的样例构建一棵树:
在这里插入图片描述

分别求出每个点到其它所有点的最长距离,在 所有距离之中取最小值

考虑一下 每个点到其它所有点距离 分为哪几类

我们可以分为 两大类

  • 第一大类:从 当前点开始往子节点走 的距离(往下
  • 第二大类:从 当前点开始往父节点走 的距离(往上

其中第一大类可以用 上一题:AcWing 1072. 树的最长路径(树形dp) 的思路求解,我们可以先求出每个点向下走的 最大距离down1[i]次大距离down2[i]

对于 第二大类情况最大值 该如何求解呢?

我们还是从 集合角度 进行分析,不妨 以树中第 2 号节点为起点,可以发现,第二类情况中所有路径的 第一步都是从 2 走到它的父节点 1之后再向任意其它节点走

在这里插入图片描述

要求所有这样形式路径的最大值,可以将其分为 两部分

  • 第一部分 2->1:显然这部分已经确定,就是 2->1 的权值
  • 第二部分 除第一部分之外的全部:从 1 号点 不返回 2 的所有路径长度的最大值。同样,第二部分又可以分为两大类,
    • 第一类 继续往上走:从父节点 1 往上走的最大长度(设为 up[1]
    • 第二类 往下走:此处又可分为两类(设为 down[1]
      • ① 如果从父节点 1 号点往下走最长的边 未经过当前点 2 号点(此时答案即为父节点 1向下走最大距离down1[1]
      • ② 如果从父节点 1 号点往下走最长的边 经过当前点 2 号点(当然再度走到 2 号点是不合法的,此时答案即为父节点 1向下走次大距离down2[1]

所以对于 第二大类,答案:up[j] = max(up[u],down[u]) + w[i]j当前节点编号u 为其 父节点编号w[i]父子节点之间已确定的边权值down[u] 根据情况 返回 父节点 u 向下走最大值down1[u] 或 次大值down2[u]

综合上面分类讨论的全部情况,循环每个节点的max值并取 min即可得到最终答案res

int res = 0x3f3f3f3f;
for(int i=1; i<=n; ++i)
{
    int dis = max(up[i], down1[i]);
    res = min(res, dis);
}

所以,本题最终做法是 跑两遍树的深度优先遍历

第一遍从上往下递归,用 子节点的信息更新父节点的信息(更新出每个节点往下走的 最大down1[i]值 和 次大down2[i]值)

第二遍 用父节点的信息将子节点的信息更新,每个子节点往上的最大距离应分两种情况分析(具体见上文)

两点注意

  • 如果题目中给出树的边权可能为负数,那么,只有当节点不是叶子节点时,才存在向下走的路径;同理只有当节点不是根节点时,才存在向上走的路径。所以最后在求最小值时,如果是叶节点,则只能用up[i]更新,如果是根节点,则只能用down1[u]更新
int res = down1[1];
for(int i=2; i<=n; ++i)
{
    if(is_leaf[i]) res = min(res, up[i]);
    else res = min(res, max(down1[i], up[i]));
}
  • ②我们在第一次向下dfs的过程中还需要存一下每个节点 最长边 是从 哪个子节点 上来的,因为后续第二次dfs时需要判断 父节点的最长边哪个子节点上来,所以我们需要开一个新的数组p1存储这个信息。

时间复杂度:

O ( n + m ) O(n + m) O(n+m)

代码:

#include<bits/stdc++.h>

using namespace std;
const int N = 1e4+10;
int n;
int h[N], e[N<<1], ne[N<<1], w[N<<1], idx;
int up[N], down1[N], down2[N];
int p1[N];

void add(int a, int b, int c)
{
    e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}

int dfs_d(int u, int father)
{
    down1[u] = down2[u] = 0;//因为题目保证都是正权边,因此初始化为 0 即可
    for(int i=h[u]; ~i; i=ne[i])
    {
        int j = e[i];
        if(j==father) continue;
        int d = dfs_d(j, u) + w[i];
        if(down1[u]<=d) {down2[u] = down1[u], down1[u] = d; p1[u] = j;}
        else if(down2[u]<d) down2[u] = d;
    }
    return down1[u];
}

void dfs_u(int u, int father)
{
    for(int i=h[u]; ~i; i=ne[i])
    {
        int j = e[i];
        if(j==father) continue;
        if(p1[u]==j) up[j] = max(up[u], down2[u]) + w[i];
        else up[j] = max(up[u], down1[u]) + w[i];
        dfs_u(j, u);
    }
}

int main()
{
    cin>>n;
    memset(h, -1, sizeof h);
    for(int i=0; i<n-1; ++i)
    {
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        add(a, b, c), add(b, a, c);
    }
    
    dfs_d(1, -1);
    
    dfs_u(1, -1);
    
    int res = 0x3f3f3f3f;
    for(int i=1; i<=n; ++i)
    {
        int dis = max(up[i], down1[i]);
        res = min(res, dis);
    }
    
    cout<<res<<endl;
    
    return 0;
}
<think>好的,用户想学习或解决AcWing平台上动态规划相关的算法问题,我需要提供相应的题解和学习资源。首先,我得回忆一下AcWing动态规划题目有哪些典型题,以及用户可能需要的学习路径。 用户提到的引用中有几个题解,比如341题最优贸易的分层图解法,这可能属于动态规划的变种。还有购物单问题,可能涉及背包DP。另外,引用4提到的离散化优化空间,可能和状态压缩有关。引用5的排队打水,可能需要贪心,但动态规划有时候也会结合贪心策略。 首先,动态规划的基础知识很重要,比如状态定义、转移方程、初始化等。用户可能需要从简单题开始,比如01背包问题,这是动态规划的经典入门题。然后逐步过渡到更复杂的问题,如完全背包、多重背包,以及像最长公共子序列、最长上升子序列这样的线性DP问题。 接下来,我需要整理AcWing上的相关题目。例如,AcWing 2. 01背包问题,这是最基础的。然后像AcWing 3. 完全背包问题,AcWing 4. 多重背包问题,这些都是背包问题的变种。还有线性DP中的AcWing 895. 最长上升子序列,AcWing 896. 最长上升子序列 II(优化版),以及区间DP问题如AcWing 282. 石子合并。 用户提到的AcWing 341最优贸易题解,使用分层图方法,这可能涉及到状态机模型,属于动态规划的一种高级应用。状态机模型将问题分解成多个状态,每个状态之间的转移对应不同的操作,比如买入和卖出。这时候可能需要分层的状态设计,每一层代表不同的状态,比如未买入、已买入未卖出、已卖出。这种方法在动态规划中用于处理有多个阶段或操作的问题。 另外,购物单问题可能属于有依赖的背包问题,比如金明的预算方案,每个主件可以有多个附件,选择主件后才能选附件。这类问题需要处理依赖关系,通常使用分组背包或树形DP来解决。 对于学习资源,用户可能需要系统的课程或题解集合。AcWing算法基础课和提高课都包含动态规划章节,适合系统学习。此外,题解区有很多高质量的解答,用户可以参考。比如,y总的讲解视频,或者其他用户的代码实现。 用户可能的疑问点包括如何定义状态,如何写出转移方程,以及如何进行空间优化。例如,在背包问题中,一维数组优化是通过逆序更新来避免重复计算。而在状态机模型中,需要明确每个状态转移的条件和计算方式。 另外,动态规划的优化技巧如单调队列、斜率优化、离散化等,可能在高级题目中出现。例如,引用4提到的离散化优化,用于减少空间复杂度,这在处理大范围数据时非常有用,比如当状态中的某些维度范围很大但实际使用较少时,可以通过离散化映射到更小的空间。 在推荐学习路径时,建议用户先掌握基本模型,再逐步挑战复杂问题。比如从背包问题开始,然后线性DP,区间DP,状态压缩DP,最后是状态机模型、树形DP等。同时,多练习相关题目,理解不同问题的状态设计和转移方式。 最后,用户可能需要具体的题解示例,比如针对某个题目的代码和思路分析。例如,对于AcWing 2. 01背包问题,可以给出二维和一维的解法,并解释状态转移的过程。对于状态机模型的问题,如AcWing 1052. 设计密码,需要详细说明状态的定义和转移条件。 总结来说,我需要提供动态规划的学习路径、典型例题、解题思路以及AcWing上的相关资源,帮助用户系统地学习和解决动态规划问题。</think>### AcWing动态规划问题学习指南 #### 一、动态规划基础知识 动态规划DP)核心在于**状态定义**和**状态转移方程**。基本步骤包括: 1. 定义状态:$dp[i][j]$ 表示某种条件下的最优解 2. 初始化边界条件 3. 建立状态转移关系:$dp[i][j] = f(dp[i-1][k], ...)$ 4. 确定最终解的位置 #### 二、经典题型分类 ##### 1. 背包问题 - **01背包**:每个物品选一次 例题:AcWing 2. 01背包问题 状态转移:$dp[j] = \max(dp[j], dp[j - v_i] + w_i)$(一维数组逆序更新)[^1] - **完全背包**:物品无限次选取 例题:AcWing 3. 完全背包问题 状态转移:$dp[j] = \max(dp[j], dp[j - v_i] + w_i)$(一维数组正序更新) - **多重背包**:物品有数量限制 例题:AcWing 4. 多重背包问题 优化方法:二进制拆分、单调队列优化 ##### 2. 线性DP - **最长上升子序列(LIS)** 例题:AcWing 895. 最长上升子序列 状态定义:$dp[i]$ 表示以第$i$个元素结尾的LIS长度 优化后时间复杂度:$O(n \log n)$[^4] - **编辑距离** 例题:AcWing 899. 编辑距离 状态转移: $$dp[i][j] = \min\left\{\begin{array}{l} dp[i-1][j] + 1, \\ dp[i][j-1] + 1, \\ dp[i-1][j-1] + (a_i \neq b_j) \end{array}\right.$$ ##### 3. 区间DP - **石子合并** 例题:AcWing 282. 石子合并 状态定义:$dp[l][r]$ 表示合并区间$[l,r]$的最小代价 转移方程: $$dp[l][r] = \min_{k=l}^{r-1}(dp[l][k] + dp[k+1][r]) + \sum_{i=l}^r a_i$$ ##### 4. 状态机模型 - **最优贸易问题** 例题:AcWing 341. 最优贸易[^2] 解法:建立三层状态表示不同交易阶段 - 第一层:未买入 - 第二层:已买入未卖出 - 第三层:已卖出 #### 三、学习资源推荐 1. **AcWing官方课程** - 算法基础课:涵盖DP基础模型 - 算法提高课:包含树形DP、状态压缩等高级内容 2. **题解资源** - AcWing题解区:搜索题目编号查看高质量题解 - 经典教材参考:《算法竞赛进阶指南》动态规划章节 3. **代码模板** ```python # 01背包一维优化模板(Python示例) n, m = map(int, input().split()) dp = [0] * (m+1) for _ in range(n): v, w = map(int, input().split()) for j in range(m, v-1, -1): dp[j] = max(dp[j], dp[j - v] + w) print(dp[m]) ``` #### 四、常见优化技巧 1. **滚动数组**:将二维状态压缩为一维 2. **离散化**:处理大范围稀疏数据(如坐标范围大但实际使用点少)[^4] 3. **单调队列优化**:适用于决策单调性问题 4. **斜率优化**:将转移方程转化为凸包问题 #### 五、练习建议 1. 从简单模板题开始(如AcWing 2, 3, 895) 2. 逐步过渡到应用题(如AcWing 341, 282) 3. 每道题尝试写出: - 状态定义 - 转移方程 - 边界条件 - 空间优化方案
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值