混合背包DP——CSP202603B. 机器人项目管理
各位好,今天我们来看CSP202603B. 机器人项目管理这道题目


题目简述
有 n 个任务,完成所有任务的基础总耗时为所有 ti 之和。现有 m 杯咖啡可供分配,每个任务喝咖啡后可缩短耗时:
-
灵活型任务:可喝任意实数杯咖啡(0 到 ai),缩短耗时与杯数成正比,每杯效率为 bi/ai
-
普通型任务:只能选择喝 ai 杯(缩短 bi)或不喝(不缩短)
求合理分配咖啡后,完成所有任务的最短总耗时。
猜想
能否直接通过每杯咖啡缩短尽可能多的时间贪心,来获得全局最优解呢?
如果全是灵活型任务,答案是肯定的。灵活型任务可以连续取值,每多给一杯咖啡就多节省 时间,不存在"浪费",所以按效率从高到低依次分配就是最优解,这是经典的分数背包问题。
但对于普通型任务,情况完全不同,我们假设有两个普通型任务:
-
任务A:需要 3 杯咖啡,节省 5 小时(效率 1.67)
-
任务B:需要 4 杯咖啡,节省 6 小时(效率 1.5)
你有 4 杯咖啡。
我们发现此时将4杯咖啡都选给效率略低的任务B才是更优的,也就是说我们不能通过贪心获得全局最优解,考虑DP
为什么需要DP
普通型只能整杯整杯取,不能拆分,这和01背包问题:每个物品要么全拿,要么不拿的情形是一样的,所以对待普通型,我们需要使用01背包获得最优解
于是整体策略变成:
-
普通型任务:用 DP 求出"消耗 k 杯咖啡,最多能节省多少时间"
-
灵活型任务:用贪心分配剩余咖啡
-
枚举分割点:尝试所有可能的 k,取总耗时最小值
把普通型任务看作 0-1 背包物品:
-
物品重量 = 需要的咖啡杯数
-
物品价值 = 节省的时间
-
背包容量 = 普通型任务所需咖啡总量和 m 的较小值
状态转移方程:
dp[j] = max(dp[j], dp[j - a_i] + b_i)
表示:消耗 j 杯咖啡时,普通型任务最多能节省 dp[j] 的时间。
枚举分给普通型的咖啡数 k(0 到背包容量):
普通型节省 = dp[k]
剩余咖啡 = m - k
灵活型节省 = 剩余咖啡按效率从高到低贪心分配
总耗时 = 基础总耗时 - 普通型节省 - 灵活型节省
具体细节看下面的参考代码:
#include <bits/stdc++.h>
using namespace std;
struct Task {
int type; // 0: 灵活型, 1: 普通型
double t; // 原始耗时
double a; // 所需咖啡杯数
double b; // 可缩短的时间
double efficiency; // 效率 b/a (灵活型使用)
};
int main() {
int n;
double m;
cin >> n >> m;
vector<Task> flexible_tasks; // 灵活型任务
vector<Task> normal_tasks; // 普通型任务
double base_time = 0; // 基础总耗时(不加速)
for (int i = 0; i < n; i++) {
int o;
double t, a, b;
cin >> o >> t >> a >> b;
base_time += t;
if (o == 0) {
// 灵活型任务
flexible_tasks.push_back({o, t, a, b, b / a});
} else {
// 普通型任务
normal_tasks.push_back({o, t, a, b, 0});
}
}
// 灵活型任务按效率降序排序(贪心准备)
sort(flexible_tasks.begin(), flexible_tasks.end(),
[](const Task& x, const Task& y) {
return x.efficiency > y.efficiency;
});
// 普通型任务:计算所需咖啡总量,DP 背包容量
int max_normal_coffee = 0;
for (auto& task : normal_tasks) {
max_normal_coffee += (int)task.a;
}
max_normal_coffee = min(max_normal_coffee, (int)m);
// 0-1 背包 DP
// dp[j] 表示消耗 j 杯咖啡时,普通型任务最多节省的时间
vector<double> dp(max_normal_coffee + 1, 0);
for (auto& task : normal_tasks) {
int weight = (int)task.a;
double value = task.b;
for (int j = max_normal_coffee; j >= weight; j--) {
dp[j] = max(dp[j], dp[j - weight] + value);
}
}
double ans = base_time;
// 枚举分配给普通型任务的咖啡数量
for (int normal_coffee = 0; normal_coffee <= max_normal_coffee; normal_coffee++) {
double remaining_coffee = m - normal_coffee;
if (remaining_coffee < 0) continue;
double time_saved = dp[normal_coffee]; // 普通型节省的时间
// 灵活型任务用剩余咖啡贪心分配
double flex_coffee = remaining_coffee;
for (auto& task : flexible_tasks) {
if (flex_coffee <= 0) break;
double use = min(flex_coffee, task.a);
time_saved += use * task.efficiency;
flex_coffee -= use;
}
ans = min(ans, base_time - time_saved);
}
cout << fixed << setprecision(10) << ans << endl;
return 0;
}
感谢阅读,欢迎在评论区讨论
转载注明出处
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐



所有评论(0)