You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

任务调度问题:从Top-Down转Bottom-Up动态规划求解

问题描述

我有一个包含每个任务成本和所需时间的列表,两台服务器分别为付费服务器与免费服务器。使用免费服务器的前提是需先在付费服务器上运行任务:付费服务器执行任务的时长为time数组指定值,免费服务器执行任意任务仅需1单位时间。需找到运行所有任务的最小成本。

我已经实现了Top-Down方式的解法:

@cache
def dfs(job_idx, time_spent):
    if job_idx == n:
        return 0 if time_spent >= 0 else float('inf')
    # 选择付费服务器:加上当前任务成本,累计时间增加time[job_idx]
    option1 = cost[job_idx] + dfs(job_idx + 1, time_spent + time[job_idx])
    # 选择免费服务器:无成本,累计时间减少1(因为免费任务消耗1单位付费时长额度)
    option2 = dfs(job_idx + 1, time_spent - 1)
    return min(option1, option2)

dfs(0, 0)

这是基于DFS的Top-Down方法,遍历所有任务分配给付费/免费服务器的可能方案。但我希望转换为Bottom-Up方法却不知如何着手——我能分别用两种方法解决DP问题,但不会相互转换。我自己总结了转换的步骤:

  1. 识别递归函数。
  2. 识别递归函数的基准情况。
  3. 创建二维数组存储子问题结果。
  4. 填充数组的基准情况(递归基准返回值)。
  5. 嵌套循环遍历子问题节点/状态,用已有结果填充剩余值。
  6. 返回初始子问题状态对应的结果。

请求帮忙解释如何针对这个问题实现两种方法的转换?


从Top-Down到Bottom-Up的转换详解

按照你总结的6个步骤,结合当前问题逐一拆解:

步骤1:识别递归函数

你的递归函数dfs(job_idx, time_spent)的核心含义是:处理到第job_idx个任务时,当前累计的付费时长余额为time_spent,此时完成剩余任务的最小成本。

  • 状态变量:
    • job_idx:当前处理的任务索引,范围是0到n(n为任务总数)
    • time_spent:累计付费时长余额,代表当前可用的、能支撑免费任务的额度
  • 状态转移逻辑:每个状态有两个选择
    • 用付费服务器:支付当前任务成本,余额增加time[job_idx](新增付费时长额度)
    • 用免费服务器:无成本,余额减少1(消耗1单位付费时长额度)

步骤2:识别递归基准情况

对应递归终止条件,当job_idx == n(所有任务处理完毕):

  • 若time_spent >= 0:免费任务的额度足够,无额外成本,返回0
  • 若time_spent < 0:免费任务用超了,不符合规则,返回无穷大(表示该状态不可行)

步骤3:创建二维数组存储子问题结果

首先确定time_spent的取值范围:

  • 最大值:所有任务都用付费服务器,余额为sum(time)
  • 最小值:所有任务都用免费服务器,余额为0 - n(初始余额为0,每个免费任务减1)

由于数组索引不能为负,我们给余额加一个偏移量offset = n,将负索引转为非负。最终创建二维DP数组dp[job_idx][balance + offset],其中:

  • job_idx范围:0到n
  • balance + offset范围:0到sum(time) + n

步骤4:填充数组的基准情况

对应递归的终止条件,当job_idx = n时:

  • 遍历所有可能的balance值,若balance >= 0,则dp[n][balance + offset] = 0
  • 若balance < 0,则dp[n][balance + offset] = float('inf')

步骤5:嵌套循环遍历子问题节点,填充剩余值

Top-Down是从job_idx=0向job_idx=n递归,Bottom-Up需要反向遍历:从job_idx = n-1到job_idx=0,这样计算当前任务状态时,下一个任务的状态已经完成计算。

遍历逻辑:

对于每个job_idx(从n-1到0):
    遍历所有可能的balance值(从-n到sum(time)):
        # 计算选择付费服务器的成本
        新余额1 = balance + time[job_idx]
        选项1成本 = cost[job_idx] + dp[job_idx+1][新余额1 + offset]
        
        # 计算选择免费服务器的成本
        新余额2 = balance - 1
        选项2成本 = dp[job_idx+1][新余额2 + offset]
        
        # 当前状态的最小成本取两者较小值
        dp[job_idx][balance + offset] = min(选项1成本, 选项2成本)

注意:如果新余额超出预设的范围(比如大于sum(time)或小于-n),直接设为无穷大表示该选择不可行。

步骤6:返回初始子问题状态对应的结果

初始状态是job_idx=0、balance=0,对应数组中的位置是dp[0][0 + offset],直接返回该值即可。

完整Bottom-Up代码示例

def min_cost(cost, time):
    n = len(cost)
    total_time = sum(time)
    offset = n  # 偏移量,处理负余额的索引问题
    max_balance = total_time
    min_balance = -n
    
    # 初始化DP数组,默认值设为无穷大
    dp = [[float('inf')] * (max_balance - min_balance + 1) for _ in range(n+1)]
    
    # 填充基准情况:所有任务处理完毕时的状态
    for balance in range(min_balance, max_balance + 1):
        if balance >= 0:
            dp[n][balance + offset] = 0
    
    # 从后往前遍历任务,填充DP数组
    for job_idx in range(n-1, -1, -1):
        for balance in range(min_balance, max_balance + 1):
            # 选项1:使用付费服务器
            new_balance1 = balance + time[job_idx]
            option1 = float('inf')
            if max_balance >= new_balance1 >= min_balance:
                option1 = cost[job_idx] + dp[job_idx+1][new_balance1 + offset]
            
            # 选项2:使用免费服务器
            new_balance2 = balance - 1
            option2 = float('inf')
            if max_balance >= new_balance2 >= min_balance:
                option2 = dp[job_idx+1][new_balance2 + offset]
            
            dp[job_idx][balance + offset] = min(option1, option2)
    
    return dp[0][0 + offset]

内容的提问来源于stack exchange,提问作者AS Scorpio

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 13:18:12