Skip to main content
Back to problems
#3562
Hard Algorithms

Maximum profit from trading stocks with discounts

Array Dynamic Programming Tree Depth-First Search
56.8% acceptance
Feb 25, 2026
375
53
You are given an integer n, representing the number of employees. Each employee is assigned a unique ID from 1 to n, and employee 1 is the CEO. present[i] is the current price, future[i] is the expected sell price. If an employee's direct boss purchases their own stock, the employee can buy at floor(present[v] / 2). Return the maximum profit without exceeding budget.

Solution

Rust
Time O(n * m)
Space O(n * m)
LeetCode
solution.rs
impl Solution {
  pub fn max_profit(
    n: i32,
    present: Vec<i32>,
    future: Vec<i32>,
    hierarchy: Vec<Vec<i32>>,
    budget: i32,
  ) -> i32 {
    let n = n as usize;
    let budget = budget as usize;

    // Build tree (1-indexed -> 0-indexed)
    let mut children: Vec<Vec<usize>> = vec![vec![]; n];
    for e in &hierarchy {
      let u = (e[0] - 1) as usize;
      let v = (e[1] - 1) as usize;
      children[u].push(v);
    }

    // Tree DP. For each node, dp[node][cost] = max profit using exactly `cost` budget
    // considering the subtree of node, given whether the parent bought (discount flag).
    // dp_with[node][cost] and dp_without[node][cost]
    // Merge children one by one via knapsack.

    // Returns (dp_no_disc, dp_disc) where dp[cost] = max profit with `cost` spent
    fn dfs(
      v: usize,
      children: &Vec<Vec<usize>>,
      present: &Vec<i32>,
      future: &Vec<i32>,
      budget: usize,
    ) -> (Vec<i64>, Vec<i64>) {
      // dp_no_disc[cost]: max profit from subtree of v, spending exactly `cost`, when parent did NOT buy
      //                   => v can be bought at full price present[v]
      // dp_disc[cost]:   max profit from subtree of v, spending exactly `cost`, when parent DID buy
      //                   => v can be bought at price floor(present[v]/2)
      // Children get discount iff WE (v) buy.
      let price_full = present[v] as usize;
      let price_disc = (present[v] / 2) as usize;
      let profit_full = (future[v] - present[v]) as i64;
      let profit_disc = future[v] as i64 - price_disc as i64;

      let mut child_results: Vec<(Vec<i64>, Vec<i64>)> = vec![];
      for &c in &children[v] {
        child_results.push(dfs(c, children, present, future, budget));
      }

      // Merge children when v is NOT bought (children get no discount) -> comb_nodisc
      // Merge children when v IS bought (children get discount) -> comb_disc
      // Each child contributes either 0 (skip) or their subtree cost.
      // We merge incrementally.
      let mut comb_disc = vec![0i64; budget + 1];
      let mut comb_nodisc = vec![0i64; budget + 1];

      for (c_no, c_yes) in &child_results {
        // comb_disc (parent bought) merges with c_yes (child's parent bought)
        let mut new_disc = vec![i64::MIN; budget + 1];
        // comb_nodisc (parent not bought) merges with c_no (child's parent not bought)
        let mut new_nodisc = vec![i64::MIN; budget + 1];
        for ca in 0..=budget {
          if comb_disc[ca] < 0 { continue; }
          // don't buy anything from child subtree (cost 0, profit 0 already in children dp[0])
          // Actually c_yes[0] = 0 (buy nothing from child subtree - that's always valid)
          for cb in 0..=(budget - ca) {
            if c_yes[cb] >= 0 {
              let v2 = comb_disc[ca] + c_yes[cb];
              if new_disc[ca + cb] < v2 { new_disc[ca + cb] = v2; }
            }
          }
        }
        for ca in 0..=budget {
          if comb_nodisc[ca] < 0 { continue; }
          for cb in 0..=(budget - ca) {
            if c_no[cb] >= 0 {
              let v2 = comb_nodisc[ca] + c_no[cb];
              if new_nodisc[ca + cb] < v2 { new_nodisc[ca + cb] = v2; }
            }
          }
        }
        // Convert MIN back to 0 for valid no-spend option
        if new_disc[0] < 0 { new_disc[0] = 0; }
        if new_nodisc[0] < 0 { new_nodisc[0] = 0; }
        comb_disc = new_disc;
        comb_nodisc = new_nodisc;
      }

      // Build dp for v based on whether v is bought or not
      // dp_no_disc: parent didn't buy v => v costs price_full
      let mut dp_no_disc = vec![0i64; budget + 1];
      for c in 0..=budget {
        // don't buy v: use comb_nodisc
        if comb_nodisc[c] >= 0 {
          dp_no_disc[c] = dp_no_disc[c].max(comb_nodisc[c]);
        }
        // buy v at full price: use comb_disc for children
        if c >= price_full && comb_disc[c - price_full] >= 0 {
          dp_no_disc[c] = dp_no_disc[c].max(profit_full + comb_disc[c - price_full]);
        }
      }

      // dp_disc: parent bought v => v costs price_disc
      let mut dp_disc = vec![0i64; budget + 1];
      for c in 0..=budget {
        // don't buy v
        if comb_nodisc[c] >= 0 {
          dp_disc[c] = dp_disc[c].max(comb_nodisc[c]);
        }
        // buy v at discounted price
        if c >= price_disc && comb_disc[c - price_disc] >= 0 {
          dp_disc[c] = dp_disc[c].max(profit_disc + comb_disc[c - price_disc]);
        }
      }

      (dp_no_disc, dp_disc)
    }

    let (dp, _) = dfs(0, &children, &present, &future, budget);
    *dp.iter().max().unwrap() as i32
  }
}