3562. 折扣价交易股票的最大利润

3562. 折扣价交易股票的最大利润 - 力扣(LeetCode)

class Solution {
public:
    int maxProfit(int n, vector<int>& present, vector<int>& future, vector<vector<int>>& hierarchy, int budget) {
        vector<vector<int>> g(n);
        for (auto& e : hierarchy)
        {
            g[e[0] - 1].push_back(e[1] - 1);
        }

        auto dfs = [&](this auto&& dfs, int x) -> vecto  r<array<int, 2>>
        {
            vector<array<int, 2>> sub_f(budget + 1);
            for (int y : g[x])
            {
                auto fy = dfs(y);
                for (int j = budget; j >= 0; j--)
                {
                    for (int jy = 0; jy <= j; jy++)
                    {
                        for (int k = 0; k < 2; k++)
                        {
                            sub_f[j][k] = max(sub_f[j][k], sub_f[j - jy][k] + fy[jy][k]);
                        }
                    }
                }
            }

            vector<array<int, 2>> f(budget + 1);
            for (int j = 0; j <= budget; j++)
            {
                for (int k = 0; k < 2; k++)
                {
                    int cost = present[x] / (k + 1);
                    if (j >= cost)
                    {
                        f[j][k] = max(sub_f[j][0], sub_f[j - cost][1] + future[x] - cost);
                    }
                    else
                    {
                        f[j][k] = sub_f[j][0];
                    }
                }
            }

            return f;
        };

        return dfs(0)[budget][0];
    }
};

1.整体

故事背景

  • 你 (x):你是某个部门的经理
  • 你的下属 (y):你管着好几个小组
  • 你的上司 (Parent):决定你是否能半价买入的大老板
  • 任务:公司给你一笔预算 budget,你要决定怎么花(分给下属多少、自己用多少),才能让部门总利润最大。

第一阶段:不仅管自己,还得管下属(sub_f 部分)

这段代码的前半部分,是你作为经理在“统筹下属”。你还没决定自己干不干,先把下属安排明白。

// 1. 准备一个账本,记录“只靠下属能赚多少钱”
vector<array<int, 2>> sub_f(budget + 1); 

// 2. 把每个下属叫进办公室谈话
for (int y : g[x]) {
    // 让下属 y 回去算账,交上来他的报价单(fy)
    auto fy = dfs(y); 
    
    // 3. 开始分钱(背包问题核心)
    // j 是假设当前手里有的总钱数
    for (int j = budget; j >= 0; j--) {
        // jy 是决定分给这个下属 y 多少钱
        for (int jy = 0; jy <= j; jy++) {
            // k 是平行宇宙:假设你自己(x)最后没买(0) 或 买了(1)
            for (int k = 0; k < 2; k++) {
                // 决策:
                // 以前的下属分剩下的钱(j-jy) + 当前下属拿到的钱(jy)
                // 哪个组合赚得多,就记下来
                sub_f[j][k] = max(sub_f[j][k], sub_f[j - jy][k] + fy[jy][k]);
            }
        }
    }
}

大白话翻译第一阶段:

“我还没决定我自己买不买(k是未知的),但我先把所有下属的方案整合好。 比如我有 100 块,我是给 A 组 30、B 组 70 赚得多?还是给 A 组 50、B 组 50 赚得多? sub_f 就是整合了所有下属之后的‘最佳分钱方案’。


第二阶段:做你自己的决定(f 部分)

现在下属的账算清了(都在 sub_f 里),轮到你做决定了:这一单,你自己做不做?

// 1. 准备最终报告,交给你的上司
vector<array<int, 2>> f(budget + 1);

// 2. 枚举所有可能的预算情况
for (int j = 0; j <= budget; j++) {
    
    // 3. 这里的 k 是指你的上司买没买(决定你的成本)
    for (int k = 0; k < 2; k++) {
        
        // 算你的进货价:上司买了(k=1)你就半价,没买(k=0)你原价
        int cost = present[x] / (k + 1);

        // 如果钱够买你自己
        if (j >= cost) {
            // 这里是全剧终极二选一:
            
            f[j][k] = max(
                // 选项 A:你不买(省钱模式)
                // 收益 = 下属们在你“不买”的情况下的最大收益
                sub_f[j][0], 
                
                // 选项 B:你买(投资模式)
                // 收益 = 下属们在你“买了”的情况下的最大收益 + 你这一单的利润
                // 注意:因为你买了,所以下属享受了你的福利,查表要查 sub_f[...][1]
                sub_f[j - cost][1] + future[x] - cost
            );
        } 
        else {
            // 钱不够,只能被迫选 A(不买)
            f[j][k] = sub_f[j][0];
        }
    }
}
return f; // 把报告交上去

大白话翻译第二阶段:

“现在我知道下属怎么干最赚钱了。 如果我不干:把钱全给下属,且下属没法打折(查 sub_f[...][0])。 如果我干:我先扣掉我的成本 cost,剩下的钱给下属,而且下属能打折(查 sub_f[...][1]),再加上我赚的钱。 这就得到了最终结果 f


总结全过程

  1. 递归 (dfs(y)):先把任务派给下属,让他们算出所有可能性的利润。
  2. 第一段循环 (sub_f)拼凑下属。不管我买不买,先算出把钱分给下属们的最佳组合。
  3. 第二段循环 (f)决定自己。结合下属的成果,决定我自己买还是不买,从而得到整组人的最大利润。

这就是树形 DP 的魅力:自底向上,每个人都把自己的小团队安排好,层层上报,最后大老板(根节点)就能得到全局最优解。

2.循环一

1. 场景设定:你是“家长”,你在分钱

  • 角色:你是家长 $x$(当前节点)。
  • 任务:你有好几个孩子(子节点 $y$),比如大儿子、二女儿、三儿子...
  • 资源:你手里有一个总预算,比如 10 块钱 (j)。
  • 目标:你要把这 10 块钱分给这些孩子,让他们各自去发挥(买东西赚钱),最后让全家的总利润最高。

但是!你不能同时把大家叫来分钱。你只能一个一个谈。


2. 逐层拆解循环(大白话版)

第一层循环:for (int y : g[x])

翻译:挨个把孩子叫进房间。

你不能乱,先叫大儿子进来谈,谈完定好了方案;再叫二女儿进来,根据大儿子的方案进行调整……

  • sub_f 就是你的记账本
  • 在大儿子进来前,本子是空的。
  • 大儿子谈完,本子上记着:“给大儿子 1 块赚多少,2 块赚多少...”。
  • 二女儿进来时,我们要把她和本子上已有的(大儿子的)数据进行合并

第二层循环:for (int j = budget; ...)

翻译:假设手里有不同金额的钱。

虽然你实际可能只有 10 块钱,但在计算时,你要把“假设我有 10 块”、“假设我有 9 块”……一直到“假设我有 0 块”的情况都算一遍。

  • 为什么?因为可能你的上级(爷爷)只给你 5 块钱呢?你得把所有可能性的方案都备好。

第三层循环:for (int jy = 0; jy <= j; jy++) —— 最核心的一步!

翻译:钱怎么切分?(左口袋 vs 右口袋)

假设现在外层循环告诉你:“现在总预算是 j(比如 10 块)”。

此时房间里有两个对象:

  1. 老二(当前子节点 y:新来的。
  2. 老大(之前的 sub_f:之前已经安排好的哥哥姐姐们。

这层循环就是在尝试所有分钱的比例

  • 情况 1:给老二 0 块 (jy=0),剩下的 10 块全给老大 (j-jy = 10)。
  • 情况 2:给老二 1 块 (jy=1),剩下的 9 块给老大 (j-jy = 9)。
  • ...
  • 情况 10:给老二 10 块 (jy=10),老大没钱了 (j-jy = 0)。

代码里的 sub_f[j - jy][k] + fy[jy][k] 就是在算:

“老大拿剩余钱赚的利润” + “老二拿当前钱赚的利润” = “现在的总利润”

第四层循环:for (int k ...)

翻译:爸爸买没买?

这个最简单。因为规则是“爸爸买了儿子半价”。

所以我们在算账时,要平行算两套账:

  • k=0:假设爸爸没买,我们分钱能赚多少?

  • k=1:假设爸爸买了,儿子们成本低了,我们分钱能赚多少?

    这两套账互不干扰,顺带算一下而已。


3. 图解模拟:合并的过程

假设现在轮到二女儿进房间了。

当前假设总预算 j = 5 块钱。

我们正在做第三层循环,尝试分配这 5 块钱:

尝试方案 给二女儿 (jy) 留给大儿子 (j - jy) 计算公式 结果
方案 A 0 元 5 元 大儿子的5元收益 + 二女儿的0元收益 比如 50
方案 B 1 元 4 元 大儿子的4元收益 + 二女儿的1元收益 比如 60
方案 C 2 元 3 元 大儿子的3元收益 + 二女儿的2元收益 比如 80 (最高!)
... ... ... ... ...
方案 F 5 元 0 元 大儿子的0元收益 + 二女儿的5元收益 比如 40

代码含义:

sub_f[j][k] = max(...)

意思就是:“在这个 5 块钱的预算下,我试遍了所有分法,发现‘给二女儿 2 块,大儿子 3 块’赚得最多(80),赶紧把 80 记在账本上!”

4. 总结

这段代码看起来吓人,其实就是“穷举分赃”

  1. y 循环:来了一个新合伙人。
  2. j 循环:手里总共有这么多本金。
  3. jy 循环切蛋糕——我是切给你多一点(jy大),还是留给老团队多一点(j-jy大)?
  4. max:哪种切法赚得多,就保留哪种。

等这个大循环跑完,你就得到了一个完美的账本,无论上级给你多少钱,你都知道怎么分配给手下的这群孩子最划算。

3.循环二

之前那些复杂的 3 层循环算出 sub_f,都是在给这段代码做铺垫。sub_f 是你的“下属们”能赚的钱,而这段代码是你(当前节点 $x$)做最终拍板的时候。

简单来说,这段代码在问自己一个问题:“我到底是买,还是不买?”

我用最直白的大白话给你翻译每一行。


1. 角色代入

  • :节点 $x$(当前正在做决策的人)。
  • k你的爸爸有没有买?(决定了你进货是不是半价)。
  • sub_f:你的下属们交上来的业绩表(前面那一大段代码算出来的)。

2. 逐行翻译

第一步:准备新账本

vector<array<int, 2>> f(budget + 1);
  • 你要交给你爸爸一张新的业绩表 f

第二步:看菜吃饭(枚举预算)

for (int j = 0; j <= budget; j++)
  • 假设手里分别有 0 元、1 元、2 元……一直到 budget 元,每种情况下我该怎么选?

第三步:看爸爸脸色(枚举父节点状态)

for (int k = 0; k < 2; k++)
  • 这里我们要分两种情况讨论,因为能不能打折全看爸爸:
    • 情况 k=0:爸爸没买。
    • 情况 k=1:爸爸买了。

第四步:算算我的进货价

int cost = present[x] / (k + 1);
  • 关键点
    • 如果 k=0(爸爸没买),cost = 原价 / 1 = 原价
    • 如果 k=1(爸爸买了),cost = 原价 / 2 = 半价

第五步:我有钱买吗?

if (j >= cost)
  • 看看当前假设的预算 j,够不够付我的进货价 cost

3. 最核心的决策时刻(MAX 对比)

这是你最看不懂的地方,也是全题的灵魂:

f[j][k] = max(sub_f[j][0], sub_f[j - cost][1] + future[x] - cost);

这是在比较两个方案,看哪个赚得多:

方案 A:我不买(省钱模式)

  • sub_f[j][0]
    • 含义:既然我决定不买,那我就不用花钱,把手里所有的预算 j 都给我的下属们去分。
    • 为什么要查 [0]:因为我没买,所以我的下属们去进货时,看到的是“爸爸(我)没买”,他们不能打折。所以我要查下属账本里“父节点未买”的那一列数据。

方案 B:我买(投资模式)

  • sub_f[j - cost][1] + future[x] - cost
    • cost:我先从预算 j 里掏出 cost 元把自己买了。
    • future[x] - cost:这是我这一单赚的纯利润。
    • sub_f[j - cost][1]
      • 剩下的钱 j - cost 扔给下属们去分。
      • 为什么要查 [1]:重点来了!因为我买了,所以我的下属们看到的是“爸爸(我)买了”,他们统统半价!所以我要查下属账本里“父节点买了”的那一列数据(通常这列赚得更多)。

结论

max(...) 就是在问:

“是我不买,全款给下属去玩赚得多?

还是我先花钱买了,再让下属享受半价福利赚得多?”


4. 没钱的情况

else {
    f[j][k] = sub_f[j][0];
}
  • 如果预算 j 连我都买不起,那就没得选了,只能执行方案 A(我不买),把钱全给下属。

总结一张图

想象你在填表 f[j][k]

此时的情景 你的决定 你的花费 你赚的钱 下属分到的钱 下属看到的你 (状态) 下属能赚多少 (查表)
方案 A 不买 0 0 All (j) 没买 (0) sub_f[j][0]
方案 B cost future - cost 剩饭 (j-cost) 买了 (1) sub_f[j-cost][1]

代码就是把这一行算式写出来了而已。

posted @ 2025-12-20 21:56  belief73  阅读(0)  评论(0)    收藏  举报