【OD机试题解法笔记】在规定时间内获得最大报酬

题目

小明每周上班都会拿到自己的工作清单,工作清单内包含n项工作,每项工作都有对应的

耗时时间(单位h)和报酬,工作的总报酬为所有已完成工作的报酬之和,那么请你帮小明安

排一下工作,保证小明在指定的工作时间内工作收入最大化。

输入描述

输入的第一行为两个正整数T,n

T代表工作时长(单位h,0<T<1000000),

n代表工作数量(1<n<=3000)

接下来是n行,每行包含两个整数t,w

t代表该工作消耗的时长(单位h,t>0),w代表该项工作的报酬

输出描述

输出小明指定工作时长内工作可获得的最大报酬

用例

输入输出
40 3
20 10
20 20
20 5
30

思考

       规定时间内可获得的最大报酬,有工作时间限制,而且每项工作可选,判断是动态规划01背包问题,把工作时长比作背包容量,每项工作比作物品,工作花费时间比作物品体积,工作对应的报酬比作物品价值,即求物品放入背包能获得的最大价值。定义dp数组,dp[i][j] 表示在规定时间 i 内有 j 项可供选择的工作能获得的最大报酬。那么状态转移方程,如果工作时间 T >= times[j], dp[i][j] = Max( dp[i-time[j]][j-1] + values[i], dp[i][j-1]),dp[i-time[j]][j-1] + value[i]) 表示选择了第 j 项工作的报酬等于前 j-1 项工作在扣除第 j 项工作时间 time[j] 条件下的最大报酬 +j 项工作的报酬 values[j]dp[i][j-1]表示不选择第 j 项工作最大报酬就等于前 j-1 项工作在时间 i 条件下的最大报酬;显然 T < times[j],工作时间不够完成当前工作,也不能选,则 dp[i][j] = dp[i][j-1]。  初始化状态工作时间为0没有报酬 dp[0][j] = 0,有工作时间没有工作也没有报酬 dp[i][0] = 0。对于 i>0  j>0 的常规状态,由于尚未开始计算选或不选工作的情况,初始值应设为 “未计算时的最小可能值”。但由于报酬为非负数(实际问题中工作报酬通常不会为负),直接用 0 作为初始值更合理—— 它既代表 “未选择任何工作时的报酬”,也能在后续计算中通过状态转移(选或不选当前工作)自然更新为更大的有效值,避免因初始值设置不当(如正负无穷)导致逻辑混。 通过两重循环自底向上迭代计算子问题,外层循环遍历工作时间范围 [1, T],内层循环遍历可选的工作项目 [1, n],完成迭代计算后 dp[T][n] 就是在规定时间 T 内有 n 份工作可选的情况下能获得的最大报酬。

算法过程

1、根据输入数据构造 times、values 数组,初始化 dp数组;

2、两重循环自底向上迭代计算子问题,迭代计算中注意判断剩余工作时间是否足够选择当前工作,时间不够就不选当前工作;

3、最终结果为 dp[T][n],时间复杂度O(n^2),空间复杂度O(n^2)

参考代码

function solution() {
  const [T, n] = readline().split(' ').map(Number);
  const times = [], values = [];
  for (let i = 1; i <= n; i++) {
    const [t, v] = readline().split(' ').map(Number);
    times[i] = t;
    values[i] = v;
  }

  const dp = Array.from({length: T+1}, () => Array(n+1).fill(0));
  for (let i = 1; i <= T; i++) {
    for (let j = 1; j <= n; j++) {
      if (i >= times[j]) { // 检查当前时间是否足够完成工作 j
        dp[i][j] = Math.max(dp[i-times[j]][j-1] + values[j], dp[i][j-1]);
      } else {
        dp[i][j] = dp[i][j-1]; // 时间不够,只能不选
      }
    }
  }
  
  console.log(dp[T][n]);
}

const cases = [
  `40 3
20 10
20 20
20 5`,
];

let caseIndex = 0;
let lineIndex = 0;

const readline = (function () {
  let lines = [];
  return function () {
    if (lineIndex === 0) {
      lines = cases[caseIndex]
        .trim()
        .split("\n")
        .map((line) => line.trim());
    }
    return lines[lineIndex++];
  };
})();

cases.forEach((_, i) => {
  caseIndex = i;
  lineIndex = 0;
  solution();
});

优化

       背包问题可以通过滚动数组把空间复杂度优化到 O(n)。定义 dp[i] 表示在工作时间 i 条件下可获得的最大报酬。第一层循环枚举每项工作,第二层循环倒序遍历工作时间,倒序遍历是个技巧,防止子问题状态覆盖问题。把判断当前工作时间是否满足选择当前工作 j 的条件作为循环的开始条件。 

参考代码

function solution() {
  const [T, n] = readline().split(' ').map(Number);
  const times = [], values = [];
  for (let i = 1; i <= n; i++) {
    const [t, v] = readline().split(' ').map(Number);
    times[i] = t;
    values[i] = v;
  }

  // 优化:仅使用一维数组保存当前列的状态
  const dp = Array(T+1).fill(0);
  for (let j = 1; j <= n; j++) {
     // 注意:为避免覆盖上一列的状态,需要从后向前遍历时间
    for (let i = T; i >= times[j]; i--) {
      dp[i] = Math.max(dp[i], dp[i-times[j]] + values[j]);
    }
  }
  
  console.log(dp[T]);
}

const cases = [
  `40 3
20 10
20 20
20 5`,
];

let caseIndex = 0;
let lineIndex = 0;

const readline = (function () {
  let lines = [];
  return function () {
    if (lineIndex === 0) {
      lines = cases[caseIndex]
        .trim()
        .split("\n")
        .map((line) => line.trim());
    }
    return lines[lineIndex++];
  };
})();

cases.forEach((_, i) => {
  caseIndex = i;
  lineIndex = 0;
  solution();
});

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值