dfn序优化树上背包

给出树上背包经典题型链接

https://www.luogu.com.cn/problem/P1273

https://www.luogu.com.cn/problem/P2014

dfn序真的很重要,这道题学到很多,既了解了dfn序这个重要内容,又学习了用其优化树上背包,另外还有一个小细节,以后最大值这样定义#define INF 0x3f3f3f3f 不要再用INT_MAX,可能计算溢出

下面贴出第二道题目的解法(dfn序加巧妙思路优化树上背包)

#include <bits/stdc++.h>

using namespace std;

#define MAXN 3005

#define MAXM 3005

#define INF 0x3f3f3f3f

// ===== 建图(链式前向星)=====

int headMAXN, nxtMAXM, toMAXM;

int cnt;

// ===== 题目数据 =====

int n, m;

int numsMAXN; // 原节点净收益(用户钱 - 边成本)

// ===== DFS + dfn =====

int dfncnt;

int valueMAXN; // dfn 对应的净收益

int sizesMAXN; // dfn 子树大小

int idMAXN; // dfn -> 原节点编号

// ===== DP =====

int dpMAXNMAXM; // dpij: dfni.. 选 j 个用户的最大净收益

void add(int u, int v) {

nxtcnt = headu;

tocnt = v;

headu = cnt++;

}

void build() {

cin >> n >> m;

cnt = 1;

dfncnt = 0;

for (int i = 0; i <= n; i++) {

headi = 0;

numsi = 0;

}

for (int i = 1; i <= n - m; i++) {

int k;

cin >> k;

while (k--) {

int v, w;

cin >> v >> w;

numsv -= w; // 边费用转移到子节点

add(i, v);

}

}

for (int i = n - m + 1; i <= n; i++) {

int w;

cin >> w;

numsi += w; // 用户付费

}

}

int dfs(int u) {

int i = ++dfncnt;

idi = u; // 记录映射

valuei = numsu;

sizesi = 1;

for (int e = headu; e; e = nxte) {

sizesi += dfs(toe);

}

return sizesi;

}

int main() {

build();

dfs(1);

// ===== DP 初始化 =====

for (int i = 1; i <= n + 1; i++)

for (int j = 0; j <= m; j++)

dpij = -INF;

dpn + 10 = 0;

// ===== 核心 DP =====

for (int i = n; i >= 1; i--) {

for (int j = 0; j <= m; j++) {

// 不选 i:整棵子树跳过

dpij = dpi + sizes\[i]j;

// 选 i

bool isUser = (idi >= n - m + 1);

int need = isUser ? 1 : 0;

if (j >= need && dpi + 1j - need != -INF) {

dpij = max(

dpij,

valuei + dpi + 1j - need

);

}

}

}

// ===== 取答案 =====

for (int j = m; j >= 0; j--) {

if (dp1j >= 0) {

cout << j << endl;

break;

}

}

return 0;

}

下面我把它叫做:

「dfn + 跳子树」树上背包模型


一、这套写法一句话定义

把树用 DFS 编号压成一条序列,
用"跳过整棵子树"来保证结构合法,
再在序列上做类似 01 背包的 DP。


二、它什么时候"能用"(✔ 必须满足)

只要 同时满足下面 4 条,你就要第一时间想到这套写法。


✅ 条件 1:图是「树 / 森林」

  • 没有环

  • 每个节点只有 一个父亲

  • 或者可以 加一个虚拟根 变成树

📌 典型表述:

  • "转播网是树状结构"

  • "每个课程最多一个先修课"

  • "公司组织结构"

  • "依赖关系构成树"


✅ 条件 2:子节点依赖父节点

也就是:

❌ 不能选子节点而不选父节点

✅ 选了子节点,父节点必须被选

📌 题面常见说法:

  • "必须先完成 A 才能做 B"

  • "信号要经过中转站才能到用户"

  • "技能树"

  • "只有买了主件才能买配件"

这是这套 DP 能成立的结构约束来源


✅ 条件 3:"选 or 不选"是核心决策

每个节点都是:

  • 要么选

  • 要么不选

而不是:

  • 选多次

  • 或连续选区间

📌 典型:

  • 选课程

  • 选用户

  • 选任务

  • 选节点

👉 本质是 0/1 决策


✅ 条件 4:目标是"数量 / 价值"的背包型目标

比如:

  • 固定选 k 个,最大价值

  • 价值 ≥ 0,数量尽量多

  • 总成本 ≤ B,选的节点最多

📌 换句话说:

你能写出一个类似 dp[i][j] 的定义


三、它什么时候"特别适合"(🔥 强烈推荐)

当你发现下面任意一条,优先用它,而不是传统树形背包


🔥 情况 1:n 很大,m 也不小

  • n ≈ 2000~3000

  • m ≈ 2000~3000

❌ 传统树形背包:

复制代码

O(n * m^2) → 直接 TLE

✅ dfn 跳子树:

复制代码

O(n * m) → 稳稳 AC

👉 这是这套写法最大的价值


🔥 情况 2:树的"顺序"无所谓,只在乎结构合法

也就是说:

  • 不关心兄弟节点谁先谁后

  • 只要求:

    • 父在子前

    • 子必须连续

👉 正是 DFS 序的强项。


🔥 情况 3:叶子 / 某类节点才是"计数目标"

比如:

  • 只数用户节点

  • 只数课程

  • 中转节点只是"通路"

📌 你这道转播题就是典型。


四、它什么时候「不适合」(⚠️ 千万别硬套)

下面这些情况,用它一定会把自己绕死


❌ 1️⃣ 一个节点可以有多个父亲(DAG)

  • 不是树

  • 比如一般的 DAG 依赖

👉 这套方法 必须保证子树是连续区间


❌ 2️⃣ 选子节点不一定要选父节点

例如:

  • "买子配件可以不买主件"

  • "跳级选课"

👉 结构约束没了,i+size[i] 的跳跃就不成立。


❌ 3️⃣ 状态依赖"兄弟之间的组合方式"

例如:

  • "同一父节点下,最多选 2 个孩子"

  • "孩子之间互斥 / 协同"

👉 那必须回到 传统树形背包合并子树


❌ 4️⃣ 问题的核心不是"选节点"

比如:

  • 路径问题

  • 最近公共祖先

  • 子树查询

👉 完全是另一类题。


五、如何一眼识别"这是这套模型的题"

你看到题目时,可以在脑子里快速过这 4 个问题

1️⃣ 结构是不是树?

2️⃣ 子节点是不是依赖父节点?

3️⃣ 是不是 0/1 选择节点?

4️⃣ 能不能定义 dp[i][j]

相关推荐
江畔柳前堤1 小时前
大语言模型分布式训练:从并行策略到万卡工程的系统梳理
人工智能·分布式·深度学习·算法·目标检测·机器学习·语言模型
Forever Nore2 小时前
LeetCode 4 寻找两个正序数组的中位数 - 二分
算法·leetcode
罗西的思考4 小时前
【OpenClaw具身硬件】MiniClaw 阅读笔记---(1)基础
人工智能·算法·机器学习
蛋先生DX5 小时前
大模型参数存储格式揭秘:BF不是男朋友
深度学习·算法·llm
爱跳舞的烤冷面5 小时前
自学嵌入式第22天(数据结构——哈希)
数据结构·算法·哈希算法
猎嘤一号6 小时前
博弈论(Game Theory)的理论、算法与工程
人工智能·算法·安全·博弈论
月光船幽幽6 小时前
影子模式下保护 logits 不被修改
人工智能·python·算法
马拉AI6 小时前
腾讯开源 Agent 记忆系统,AI“换对话就忘”的问题有了新解法(附安装使用教程)
人工智能·算法·开源·科研
地平线开发者8 小时前
征程6工具链模型X86推理方式说明
算法
地平线开发者8 小时前
【征程6】校准量化中HistogramObserver解析
算法