基于整数规划的助教排班约束合理建模方法问询
这是排班类整数规划问题里非常典型的建模困境——变量设计和约束表达的权衡直接决定了求解器的效率,我来分享几个经过实践验证的优化方向:
1. 回到基础0/1变量,补上「班次连续性」约束
你第一次的模型变量(员工×时段)其实是最简洁的,问题出在没限制班次必须连续。只要补上连续性约束,就能避免零散的半小时班次,同时保持变量规模可控(30×150=4500个变量,完全在求解器的处理范围内)。
具体可以这么做:
- 定义核心变量:
x[i,t] ∈ {0,1},表示员工i在半小时时段t是否在岗 - 加班次连续性约束:禁止员工出现「在岗→离岗→在岗」的零散情况,通过辅助变量简化表达:
- 设
s[i,t] ∈ {0,1}:员工i在时段t开始一个新班次(即x[i,t]=1且x[i,t-1]=0,时段1单独处理) - 设
e[i,t] ∈ {0,1}:员工i在时段t结束一个班次(即x[i,t]=1且x[i,t+1]=0,最后一个时段单独处理) - 约束1:每个员工每天的班次开始次数≤1(或你需要的2次):
sum(s[i,t] for t in 当日时段) ≤ 1 - 约束2:班次的开始和结束必须匹配:
sum(s[i,t] for t in 当日时段) = sum(e[i,t] for t in 当日时段) - 约束3:强制
x[i,t]的连续性:比如对t>1,x[i,t] ≤ x[i,t-1] + s[i,t](如果当前在岗,要么之前就在岗,要么是新班次开始);对t<T,x[i,t] ≤ x[i,t+1] + e[i,t](如果当前在岗,要么之后继续在岗,要么是班次结束)
- 设
这种方式的变量总数是3×30×150=13500,远少于你第二次尝试的「员工×开始时间×时长」变量,求解器的分支定界效率会高很多。
2. 优化「无重叠班次」的约束表达(如果坚持用长班次变量)
如果你还是想用「员工×开始时间×时长」的变量,那问题出在重叠约束的枚举方式太低效——直接枚举所有可能的班次对会导致约束爆炸,内存自然不够。
换一种思路:对每个员工i和每个时段t,计算覆盖t的所有班次变量的和≤1。比如:
- 定义变量
y[i,s,d] ∈ {0,1}:员工i从时段s开始,连续工作d个半小时块 - 约束:对每个i、t,
sum(y[i,s,d] for s ≤ t < s+d) ≤ 1
这样约束总数是30×150=4500,比枚举班次对的约束数(可能几十万)少太多,内存压力会大幅降低。
3. 利用求解器参数和模型简化技巧
针对你用的COIN-CBC(或lpsolve),可以调整几个关键参数来加速求解:
- 开启裁剪(cuts):CBC默认会启用一些裁剪,但可以手动开启更多(比如
CbcSetIntegerCuts),帮助缩小可行解空间 - 设置时间限制先找可行解:如果不需要全局最优,先让求解器快速找到一个可行解,再逐步优化;或者用启发式算法先生成一个初始解,再让求解器迭代改进
- 拆分问题:按天独立排班,再整合结果——每天的问题规模只有30×(150/天数),求解速度会快很多,之后再处理跨天的约束(如果有的话)
4. 适配你的效用最大化目标
不管用哪种变量设计,效用函数都可以直接对应:
- 用
x[i,t]变量:总效用就是sum(u[i,t] * x[i,t] for all i,t),其中u[i,t]是员工i在时段t的效用值 - 用
y[i,s,d]变量:总效用是sum(v[i,s,d] * y[i,s,d] for all i,s,d),其中v[i,s,d]是员工i从s开始工作d个时段的总效用(可以是单个时段效用的和,也可以自定义)
这种建模方式既满足公平性要求,又能让求解器高效处理,30个员工+150个时段的规模完全没问题。
内容的提问来源于stack exchange,提问作者Norman Ramsey




