基于Google OR-Tools的志愿者排班:如何实现连续工作日偏好?
志愿者排班系统:连续工作日偏好的实现技巧
核心思路是给“非连续排班导致的额外出行”添加惩罚项,让模型优先选择连续天数更多的方案——因为连续排班的总惩罚更低,会被目标函数优先选中。
具体实现步骤
1. 定义辅助变量
针对每个志愿者和每一天,定义start[v,d]布尔变量:表示志愿者v在第d天开启了一个新的工作周期(即当天上班,且前一天休息;第一天上班直接算开启新周期)。这个变量的取值直接对应一次出行行为。
2. 添加约束关联排班变量
通过约束确保start[v,d]的取值逻辑正确:
- 第一天:若志愿者当天上班,则
start[v,0] = 1,否则为0 - 后续天数:仅当当天上班且前一天休息时,
start[v,d] = 1
3. 加入目标函数的惩罚项
给每个start[v,d]设置一个惩罚系数(可根据业务优先级调整),将所有start变量的惩罚总和加入原目标函数,让模型最小化总惩罚(即最小化出行次数)。
C#代码示例
// 假设x[v,d]是已定义的布尔变量,表示志愿者v在第d天是否上班 // 初始化参数 int totalDays = 5; // 示例总天数 int volunteerCount = 10; // 示例志愿者数量 int startPenalty = 10; // 出行惩罚系数,值越大越优先连续排班 // 定义start变量字典 var startVars = new Dictionary<(int Volunteer, int Day), BoolVar>(); foreach (int v in Enumerable.Range(0, volunteerCount)) { // 处理第一天的start逻辑 startVars[(v, 0)] = model.NewBoolVar($"start_v{v}_d0"); model.Add(startVars[(v, 0)] == x[v, 0]); // 处理后续天数的start逻辑 foreach (int d in Enumerable.Range(1, totalDays - 1)) { startVars[(v, d)] = model.NewBoolVar($"start_v{v}_d{d}"); // 约束:仅当d天上班且d-1天休息时,start为1 model.Add(startVars[(v, d)] >= x[v, d] - x[v, d - 1]); model.Add(startVars[(v, d)] <= x[v, d]); model.Add(startVars[(v, d)] <= 1 - x[v, d - 1]); } } // 计算出行惩罚总和 var startPenaltySum = LinearExpr.Sum(startVars.Values.Select(var => var * startPenalty)); // 更新目标函数,加入出行惩罚 model.Minimize(objBoolSum + objIntSum + startPenaltySum);
关键调整点
- 惩罚系数
startPenalty:如果希望减少出行的优先级高于班次覆盖率,将该值设得比低覆盖率的惩罚系数大;反之则调小,确保模型优先级符合业务需求。 - 扩展优化:如果需要进一步鼓励更长的连续排班(比如3天连续优于2天连续),可以额外添加针对“短连续周期”的惩罚,但仅通过最小化
start变量的方式,已经能实现你要求的排班优先级:[Yes, Yes, Yes, No, No](1次出行)<[Yes, No, Yes, Yes, No](2次出行)<[Yes, No, Yes, No, Yes](3次出行)。
内容的提问来源于stack exchange,提问作者CragMonkey
相关产品推荐
相关产品推荐

