烙饼
烙饼问题(可抢占并行机调度)思路整理
整数规划特例
一、题目简述
有 n 个饼,需要烙制时间分别为 a[1..n];有 m 台完全相同的烙饼机,每台同时只能烙一个饼,且一个饼可被中断(即在某台机器上烙一会儿后取出,以后在任意机器上继续烙),每次烙制时间必须是整数分钟。要求制定一份烙制记录,使得所有饼的最晚完成时间(makespan)最小,并且记录条数 k 满足 1 ≤ k ≤ 2n。输出任意最优方案。
二、核心思路
1. 理论最优时间下界
设总烙制时间 S = sum(a),单个饼所需的最大时间为 mx = max(a)。
- 因为共有
m台机器,总工作量S必须由它们分担,因此任何方案的最晚完成时间T至少为ceil(S / m)。 - 又因为同一个饼不能被同时烙,所以任何方案的最晚完成时间至少为
mx。
所以最优时间 T 的下界为:
T = max( mx, ceil(S / m) )
这个下界是可达的(因为允许抢占),因此它就是真正的最优解。
三、构造最优方案(McNaughton 算法)
1. 将总工作量拉成一根时间轴
想象将所有饼按顺序首尾相连排成一根长度为 S 的时间轴,第 i 个饼占据长度为 a[i] 的一段。
2. 按机器容量切分
把这根时间轴从起点 0 开始,每隔 T 分钟切一刀,总共切成 m 段(最后一段可能不满):
- 第
1段:[0, T]→ 分配给机器1 - 第
2段:[T, 2T]→ 分配给机器2 - ……
- 第
m段:[(m-1)T, mT]→ 分配给机器m
因为 mT >= S,所以所有饼都能被放入这 m 段中。
3. 生成烙制记录
按输入顺序处理每个饼,维护当前机器编号 mach 和该机器已用时间 cur。
对于当前饼,剩余需要 need:
- 如果
cur + need <= T,则在该机器上一次完成:记录(饼id, mach, cur, cur+need),并更新cur += need。 - 否则,先在本机器上烙到
T:记录(饼id, mach, cur, T),need -= T - cur,然后换下一台机器mach++,cur = 0,继续处理剩余的need。
由于 need <= mx <= T,所以一个饼最多只会被分成两段,记录数 k <= n + (m-1)。
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
struct Record {
int id1; // 饼编号
int id2; // 烙饼机编号
ll l, r; // 时间段 [l, r)
};
int main() {
ios::sync_with_stdio(false); cin.tie(nullptr);
int n, m; cin >> n >> m;
vector<ll> a(n);
ll sum = 0, mx = 0;
for(int i = 0; i < n; i ++) {
cin >> a[i];
sum += a[i];
mx = max(mx, a[i]);
}
// 理论最短时间
ll T = max(mx, (sum + m - 1) / m);
vector<Record> ans;
int mach = 1;
ll cur = 0;
for(int i = 0; i < n; i ++) {
ll need = a[i];
while(need > 0) {
if(cur == T) {mach ++;cur = 0;}
ll take = min(need, T - cur);
ans.push_back({i + 1, mach, cur, cur + take});
need -= take;
cur += take;
}
}
cout << ans.size() << '\n';
for(auto &rec : ans)
cout << rec.id1 << ' ' << rec.id2 << ' ' << rec.l << ' ' << rec.r << '\n';
return 0;
}

浙公网安备 33010602011771号