把每个任务的选择压成 A 机器总时间这一维,设 dp[x] 表示 A 用时为 x 时 B 的最小用时,最后在所有状态里取 max(A,B) 的最小值。
OJ: luogu
题目 ID: P2224
难度:普及+/提高
标签:动态规划背包状态设计分类讨论
日期: 2026-06-21 09:30
题意
有 n 个任务,每个任务有三种可能的加工方式:
- 只用 A 机器,耗时
t1 - 只用 B 机器,耗时
t2 - A、B 两台机器同时加工,耗时
t3
其中题面里的 0 表示这种加工方式不可用,不是“耗时为 0”。
要求给每个任务选一种可用方式,使完成全部任务所需的总时间最少。
思路
先看一个可以直接验证想法的朴素解:
cpp
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 25;
const int INF = 1e9;
int n;
int t1[MAXN], t2[MAXN], t3[MAXN];
int answer;
void dfs(int idx, int sum_a, int sum_b) {
if (idx > n) {
answer = min(answer, max(sum_a, sum_b));
return;
}
if (t1[idx] > 0) {
dfs(idx + 1, sum_a + t1[idx], sum_b);
}
if (t2[idx] > 0) {
dfs(idx + 1, sum_a, sum_b + t2[idx]);
}
if (t3[idx] > 0) {
dfs(idx + 1, sum_a + t3[idx], sum_b + t3[idx]);
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
// brute.cpp:小数据暴力解,用来帮助理解题意并辅助对拍。
// 每个任务枚举三种可用加工方式:
// 只给 A、只给 B、或者 A/B 同时做。
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> t1[i] >> t2[i] >> t3[i];
}
answer = INF;
dfs(1, 0, 0);
cout << answer << '\n';
return 0;
}暴力做法就是对每个任务枚举三种可用模式,最后统计:
- A 机器总用时
sumA - B 机器总用时
sumB
由于两台机器可以并行工作,所以全部任务完成所需的总时间就是:
max(sumA, sumB)
于是题目变成:
给每个任务选一种模式,使
max(sumA, sumB)最小。
这是一个很典型的“双机负载平衡”模型。
我们只保留一维状态即可。
设:
dp[x] = A 机器总用时恰好为 x 时,B 机器总用时的最小值
处理每个任务时有三种转移:
- 只给 A 做:
x -> x + t1 - 只给 B 做:
dp[x] + t2 - A、B 一起做:
x -> x + t3,同时dp[x] + t3
第三种方式会同时增加两台机器的负载,这是这题最关键也最容易漏掉的地方。
所有任务处理完后,枚举每个 x,答案就是:
min max(x, dp[x])
代码
cpp
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 6005;
const int MAXS = 30005;
const int INF = 1e9;
int n;
int t1[MAXN], t2[MAXN], t3[MAXN];
int dp[MAXS], ndp[MAXS];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n;
int sum_limit = 0;
for (int i = 1; i <= n; i++) {
cin >> t1[i] >> t2[i] >> t3[i];
sum_limit += max(t1[i], t3[i]);
}
for (int i = 0; i <= sum_limit; i++) {
dp[i] = INF;
}
dp[0] = 0;
int cur_limit = 0;
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= sum_limit; j++) {
ndp[j] = INF;
}
for (int a_time = 0; a_time <= cur_limit; a_time++) {
if (dp[a_time] == INF) {
continue;
}
// 当前任务只由 A 机器完成。
if (t1[i] > 0) {
ndp[a_time + t1[i]] = min(ndp[a_time + t1[i]], dp[a_time]);
}
// 当前任务只由 B 机器完成。
if (t2[i] > 0) {
ndp[a_time] = min(ndp[a_time], dp[a_time] + t2[i]);
}
// 当前任务由 A、B 两台机器共同完成。
if (t3[i] > 0) {
ndp[a_time + t3[i]] = min(ndp[a_time + t3[i]], dp[a_time] + t3[i]);
}
}
cur_limit += max(t1[i], t3[i]);
for (int j = 0; j <= cur_limit; j++) {
dp[j] = ndp[j];
}
}
int answer = INF;
for (int a_time = 0; a_time <= cur_limit; a_time++) {
if (dp[a_time] == INF) {
continue;
}
answer = min(answer, max(a_time, dp[a_time]));
}
cout << answer << '\n';
return 0;
}复杂度
设 S = sum(max(t1_i, t3_i))。
- 时间复杂度
- 空间复杂度
因为 t1,t2,t3 <= 5,所以 S <= 30000,可以通过。
总结
这题看上去像调度题,但本质不是“排顺序”,而是“选模式”。
一旦把每个任务对两台机器的贡献看成:
- 只加到 A
- 只加到 B
- 同时加到 A 和 B
就能自然写出一维负载 DP。
一图流解析
这张图把本题的建模、关键转移、实现检查和训练方法压缩到一页,适合读完正文后复盘。
