树形DP——AcWing 323. 战略游戏

树形DP

定义

树形动态规划(Tree Dynamic Programming,简称树形DP)是一种在树形结构上应用动态规划算法的技术。它利用树的递归结构,通过定义状态和状态转移方程,来求解与树相关的最优化问题,如树上的最长路径、最小路径覆盖、最大独立集等。与传统的线性动态规划相比,树形DP更侧重于利用树的子结构特性,递归地解决问题,从叶子节点到根节点或反之进行状态的累积和更新。

运用情况

  1. 树的最长路径问题:给定一个树,找出一条路径,使得该路径上所有边的权值之和最大。
  2. 数字转换问题:在不超过 n 的正整数范围内进行数字变换,求不断进行数字变换且不出现重复数字的最多变换步数。
  3. 树的中心问题:给定一棵树,找到一个点,使得该点到树中其他结点的最远距离最近。
  4. 没有上司的舞会问题:在一棵以校长为根的树中,每个职员有一个快乐指数,且没有职员愿意和直接上司一起参会,求邀请一部分职员参会使得所有参会职员的快乐指数总和最大。
  5. 路径问题:如求解树中两个节点间的最大距离、树的直径等。
  6. 子树问题:找出具有特定性质的子树,如最大权独立集、最小割集等。
  7. 计数问题:统计满足特定条件的子树数量,如完美子树的数量、具有特定性质的路径数量等。
  8. 优化问题:在树上分配资源,使得某个指标最大化或最小化,如最小化涂色成本、最大化收集的资源等。

注意事项

  1. 树形 DP 的 for 循环能优化就优化,比如取 j=min(size(x),m)k<=min(size(x),m) 之类的,否则很容易 TLE。
  2. 要考虑清楚不合法状态是否会对答案产生影响,如果有就要 memset(dp,-1,sizeof(dp)) 和初始化,树形 DP 中跳过 dp(x)(j)=-1dp(x)(k-j)=-1 之类情况。
  3. 状态定义:正确定义状态是关键,通常包括节点的选择状态(是否包含当前节点)和其他与问题相关的附加信息。
  4. 状态转移:明确状态之间的依赖关系,设计合理的状态转移方程,通常从子节点到父节点进行信息传递。
  5. 边界条件:处理好边界情况,如树的叶子节点或只有一个节点的子树。
  6. 记忆化搜索:由于树形DP往往涉及大量的重复计算,使用记忆化搜索可以避免重复计算,提高效率。
  7. 递归/迭代实现:根据具体情况选择合适的实现方式,递归通常更直观,迭代则可能在空间复杂度上有优势。

解题思路

  1. 确定状态表示:需要根据具体问题确定状态表示,通常是以节点为基本单位,记录每个节点的相关信息。
  2. 定义状态转移方程:根据问题的要求,确定状态之间的转移关系,即如何从一个状态转移到另一个状态。
  3. 确定边界条件:明确问题的边界情况,例如根节点的状态或者叶子节点的状态。
  4. 进行递归计算:根据状态转移方程,从根节点开始递归地计算每个节点的状态。
  5. 求解最优解:根据计算得到的状态值,求解问题的最优解。
  6. 分析问题:首先明确问题的求解目标,识别出问题中的"最优子结构",即问题的解可以由其子问题的解组合得出。
  7. 定义状态:基于问题特点,定义状态表示每个节点或子树在求解过程中的信息,通常包括是否选择该节点、子树的某些属性等。
  8. 设计状态转移方程:根据问题性质,确定状态如何从子树转移到父节点,即如何通过子节点的信息计算出父节点的信息。
  9. 初始化:确定基础情况,如叶子节点的初始状态。
  10. 实现:使用深度优先搜索(DFS)或广度优先搜索(BFS)遍历树,并根据状态转移方程计算每个状态的值,通常使用记忆化或递推实现。
  11. 回溯获取答案:根据最终状态,回溯获得问题的最优解。

AcWing 323. 战略游戏

题目描述

活动 - AcWingAcWing 323. 战略游戏 - AcWing活动 - AcWing

运行代码

cpp 复制代码
#include <iostream>
#include <cstring>
using namespace std;
const int N = 2000, M = 4000;
int h[N], e[M], ne[M], idx;
int n;
int f[N][2];
int st[N];
void add(int a, int b)
{
    e[idx] = b; ne[idx] = h[a]; h[a] = idx ++;
}
void dfs(int u)
{
    f[u][1] = 1, f[u][0] = 0;
    for(int i = h[u]; ~i; i = ne[i])
    {
        int j = e[i];
        dfs(j);
        f[u][0] += f[j][1];
        f[u][1] += min(f[j][0], f[j][1]);
    }
}
int main()
{
    while(scanf("%d", &n) == 1)
    {
        memset(h, -1, sizeof h);
        memset(st, 0, sizeof st);
        idx = 0;
        for(int i = 0; i < n; i ++ )
        {
            int id, cnt;
            scanf("%d:(%d)", &id, &cnt);
            while(cnt --)
            {
                int ver;
                scanf("%d", &ver);
                add(id, ver);
                st[ver] = true;
            }
        }
        int root = 0;
        while(st[root]) root ++;
        dfs(root);
        printf("%d\n", min(f[root][0], f[root][1]));
    }
    return 0;
}

代码思路

  1. 数据结构定义:

    • 使用邻接表表示树结构,其中h[N]是头节点数组,e[M]是边的终点数组,ne[M]是下一个兄弟节点的索引数组,idx用于记录当前使用的边的索引。
    • f[N][2]是一个二维状态数组,其中f[u][0]表示以节点u为根的子树中选择不包含该节点的方案数,f[u][1]表示包含该节点的方案数。
    • st[N]标记数组,用来记录某个节点是否已被其他节点直接连接过,辅助找到树的根节点。
  2. 输入处理:

    • 读取整数n表示节点数量。
    • 接收每行输入,格式为"节点ID:(直接子节点数量) 子节点1 子节点2 ...",并构建邻接表表示的树结构。
  3. 寻找根节点 :通过遍历st[]数组找到没有直接父节点的节点作为树的根,初始化为0。

  4. 深度优先搜索(DFS):

    • 从根节点开始进行深度优先搜索,递归遍历整棵树。
    • 对于每个节点u,先假设自己不被选中(f[u][1]=1,表示以自己为根的子树至少有一种情况是选中自己的),不选中父节点的方案数默认为0(f[u][0]=0)。
    • 遍历所有子节点,递归调用DFS更新f[u][0]f[u][1]的值。f[u][0] += f[j][1]意味着考虑不选当前节点时,子节点可选择不包含自己的方案数累加;f[u][1] += min(f[j][0], f[j][1])则是考虑选当前节点时,子节点可以自由选择是否包含自己,取最小值是因为我们要找的是最小方案数。
  5. 输出结果 :最终,输出根节点的f[root][0]f[root][1]中的最小值,即为满足条件的最小方案数。

改进思路

  1. 增加注释:提高代码的可读性,特别是对于复杂逻辑的部分,通过注释说明每段代码的目的。
  2. 变量命名清晰:使变量名更具描述性,便于理解每个变量的用途。
  3. 常量分离:将数组大小等硬编码的常量提取为定义在顶部的常量,便于维护和修改。
  4. 函数封装:将部分功能(如读取树的构建)封装成独立的函数,提高代码模块化。
  5. 去除全局变量的过度依赖:尽量减少全局变量的使用,使用函数参数传递必要信息。62.
相关推荐
pianmian12 小时前
python数据结构基础(7)
数据结构·算法
好奇龙猫4 小时前
【学习AI-相关路程-mnist手写数字分类-win-硬件:windows-自我学习AI-实验步骤-全连接神经网络(BPnetwork)-操作流程(3) 】
人工智能·算法
sp_fyf_20244 小时前
计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-11-01
人工智能·深度学习·神经网络·算法·机器学习·语言模型·数据挖掘
香菜大丸5 小时前
链表的归并排序
数据结构·算法·链表
jrrz08285 小时前
LeetCode 热题100(七)【链表】(1)
数据结构·c++·算法·leetcode·链表
oliveira-time5 小时前
golang学习2
算法
南宫生6 小时前
贪心算法习题其四【力扣】【算法学习day.21】
学习·算法·leetcode·链表·贪心算法
懒惰才能让科技进步7 小时前
从零学习大模型(十二)-----基于梯度的重要性剪枝(Gradient-based Pruning)
人工智能·深度学习·学习·算法·chatgpt·transformer·剪枝
Ni-Guvara7 小时前
函数对象笔记
c++·算法
泉崎7 小时前
11.7比赛总结
数据结构·算法