题目
小明每周上班都会拿到自己的工作清单,工作清单内包含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();
});