案例 054 午高峰两小时怎么排班
案例 054 午高峰两小时怎么排班
摘要:午高峰 11:00—13:00 共 936 单,按每人每 12 min 送 6 单的产能折成需求曲线,峰值 23.83 人、谷底 7.67 人,两小时共需 1872 人·min 在岗。每班只有 40 min,起班时刻可在 10:52—12:32 之间每 4 min 一档前移或后移。把它写成"整数人数 + 覆盖缺口"的两阶段整数规划:先最小化缺口,再在缺口最小的前提下最小化人力成本。最优解用 52 个班次、728 元做到零缺口;把够用的班次平摊到全部候选档位(均匀排班)只要 658 元,却留下 192.7 人·min 缺口,相当于丢掉 96 单,整体满足率只有 89.7%;按峰值配人(72 班、1008 元)缺口为零,但白付 1008 人·min 空转。均匀排班的总运力(1880 人·min)其实已经超过需求(1872 人·min),缺口却依然存在——缺的不是人,是"人在正确时刻"的匹配。
关键词:班次切分;人数配置;覆盖缺口;整数规划;字典序目标;午高峰
1 问题提出
1.1 问题是什么
午高峰两小时被切成 10 个 12 min 时段,订单量从 11:00 的 46 单爬到 12:00 的 143 单,再回落到 52 单,是一条典型的尖峰曲线。按单人单时段产能 6 单折算,需求人数从 7.67 人涨到 23.83 人,峰谷比 3.1 倍。
平台给的班次只有 40 min,不设固定班表,起班时刻由站点自己排。站点手里只有二十来个起班档位(本文取 10:52—12:32、每 4 min 一档,共 26 档),每档配几个人是整数决策。表 1 是需求与最优方案的在岗人数。
表 1 午高峰十个时段的订单量、需求骑手与优化方案的在岗人数
| 时段 | 订单 / 单 | 需求骑手 / 人 | 优化在岗 / 人 | 缺口 / 人 | 富余 / 人 |
|---|---|---|---|---|---|
| 1 11:00—11:12 | 46 | 7.67 | 8.00 | 0.00 | 0.33 |
| 2 11:12—11:24 | 63 | 10.50 | 11.00 | 0.00 | 0.50 |
| 3 11:24—11:36 | 88 | 14.67 | 15.00 | 0.00 | 0.33 |
| 4 11:36—11:48 | 112 | 18.67 | 19.00 | 0.00 | 0.33 |
| 5 11:48—12:00 | 131 | 21.83 | 22.00 | 0.00 | 0.17 |
| 6 12:00—12:12 | 143 | 23.83 | 24.00 | 0.00 | 0.17 |
| 7 12:12—12:24 | 124 | 20.67 | 24.00 | 0.00 | 3.33 |
| 8 12:24—12:36 | 101 | 16.83 | 25.00 | 0.00 | 8.17 |
| 9 12:36—12:48 | 76 | 12.67 | 19.00 | 0.00 | 6.33 |
| 10 12:48—13:00 | 52 | 8.67 | 13.00 | 0.00 | 4.33 |
1.2 为什么要优化
第一,人少了直接掉单。峰值 12:00—12:12 要 23.83 人,如果那时只有 15 人,就有一整截运力缺口,超时、拒单、差评都从这里长出来。
第二,人多了纯烧钱。单班工资 14.00 元,多排 10 个班就是 140 元,换来的是骑手在路边刷手机。
第三,40 min 的班次比人数更难排。班次一短,起班点必须错开才能贴住尖峰;班次一长,尖峰前后必然溢出。切分方式本身就是决策变量,不是排完人再补的细节。
1.3 Gurobi 如何求解
先把 120 min 离散成 30 个 4 min 槽,起班点落在槽边界上,于是一个档位覆盖哪几个槽完全由 0-1 矩阵 \(a_{t,s}\) 决定。决策变量是每档的人数 \(x_{s}\in\mathbb{Z}_{+}\),缺口 \(g_{t}\ge 0\) 是连续量。目标分两阶段:先解式 (1) 拿到最小缺口 \(G^{*}\),再加约束 \(\sum_t g_{t}\le G^{*}+\epsilon\) 解式 (5) 压成本。模型 26 个整数变量、30 条覆盖约束,单次求解 0.1 s 内结束;班次长度、时薪、订单上浮、罚金四组扫描共 39 次求解,全部 OPTIMAL,总墙钟 2 s。
2 数学模型
2.1 符号说明
\(\mathcal{S}\) 为候选起班点集合(26 档),\(\mathcal{T}\) 为 4 min 槽集合(30 个);\(x_{s}\) 为档位 \(s\) 的骑手数(非负整数),\(g_{t}\) 为槽 \(t\) 的缺口人数,\(d_{t}\) 为槽 \(t\) 的需求人数,\(a_{t,s}\) 为覆盖指示,\(L\) 为班次长度,\(q_{p}\) 为第 \(p\) 时段订单量,\(\varphi\) 为单人产能,\(\alpha\) 为订单上浮系数,\(w\) 为时薪,\(c\) 为单班成本,\(G^{*}\) 为最小缺口。
2.2 目标函数
第一阶段只管缺口:每个 4 min 槽少几个人就把这几个人计进 \(G\)。不把缺口和工资强行折算成一个加权目标,是因为两者没有天然兑换率——先替站点拍一个价,结论就变成了那个价的结论。字典序则把"先保覆盖、再省钱"这条业务规则原样搬进模型。
2.3 约束条件
每个槽的在岗人数加缺口不小于需求,缺口只许为正,于是 \(g_{t}=\max(0,\ d_{t}-\sum_s a_{t,s}x_{s})\) 被精确线性化。
覆盖矩阵按"起班后连续 \(L\) 分钟在岗"生成;\(s<0\) 的档位(10:52、10:56)只有落在 11:00 之后的部分计入覆盖,早到的时间不计产能——这就是"提前起班"要付的代价。
需求由订单量折算,\(p(t)=\lfloor t/3\rfloor+1\) 把 30 个槽映射回 10 个 12 min 时段,\(\alpha\) 用于后文的订单上浮实验。
第二阶段在缺口不超过 \(G^{*}\) 的可行域里压成本,\(c=wL/60\) 是单班工资。两阶段合起来就是"缺口优先、成本其次"的字典序。
均匀排班是把够用的总班次 \(N\) 平摊到 26 个档位,余数从前面的档位补齐;按峰值排班则是 11:00、11:40、12:20 三批各配 \(\lceil d_{\max}\rceil\) 人。
边际贡献:从最优解里拿掉档位 \(s\) 的 1 个人,缺口涨多少就是这个班次的真实价值,用来判断哪一班最不该动。
2.4 规模与性质
26 个整数变量、30 个连续缺口变量、30 条覆盖约束,是一个小规模覆盖型整数规划。结构是"区间覆盖 + 整数人数",系数矩阵全 0-1,LP 松弛的整数性很好,分支定界几乎不掉节点。难点不在求解,而在起班点是离散的:档位加密到 4 min 一档,可行域才够细;放宽到 12 min 一档,缺口立刻跳起来。
3 Gurobi 求解实现
3.1 建模要点
覆盖矩阵先用 NumPy 算成 0-1 数组,进模型时按行取系数,避免在约束里写下标判断:
A = cov(L) # 30 x 23 的 0-1 覆盖矩阵
for t in range(NT):
md.addConstr(gp.quicksum(A[t, j]*x[j] for j in range(NS)) + g[t] >= d[t])
md.setObjective(gp.quicksum(g[t] for t in range(NT)), GRB.MINIMIZE)
md.optimize(); Gs = md.ObjVal # 第一阶段:最小缺口
md.addConstr(gp.quicksum(g[t] for t in range(NT)) <= Gs + 1e-6)
md.setObjective(c*gp.quicksum(x[j] for j in range(NS)), GRB.MINIMIZE)
md.optimize() # 第二阶段:缺口封顶后压成本
两阶段之间用 \(G^{*}+10^{-6}\) 而不是 \(G^{*}\),是为了躲开浮点比较把可行域切空;MIPGap 设 1e-9,保证第二阶段拿到整数最优而不是差不多。起班人数上界给 60,远大于任何合理配置,等于不限。
3.2 求解结果
表 2 三种排班方案的成本、缺口与人数
| 方案 | 班次数 / 班 | 人力成本 / 元 | 覆盖缺口 / 人·min | 需求满足率 | 富余 / 人·min |
|---|---|---|---|---|---|
| 均匀排班 | 47 | 658.0 | 192.7 | 89.7% | 201 |
| 按峰值排班 | 72 | 1008.0 | 0.0 | 100.0% | 1008 |
| 优化排班 | 52 | 728.0 | 0.0 | 100.0% | 208 |
优化方案用 52 个班次、728 元做到缺口 0.0 人·min,在岗总时长 2080 人·min 对需求 1872 人·min,富余仅 208 人·min。均匀排班便宜 70 元,但缺口 192.7 人·min;按峰值排班多花 280 元,缺口同样为零,富余却高达 1008 人·min。
4 结果分析

图 1 优化方案:各起班档位上的 40 min 班次(色深与条内数字为人数)
4.1 班次结构 图 1 把 11 个非零档位画成甘特条:横轴 11:00—13:00,纵轴起班档位,条长固定 40 min,色深与条内数字是人数。左侧灰底是 11:00 之前的时段——模型在那里一个人都没排,因为提前起班只会把 40 min 的前 8 min 浪费在没有订单的路上。尖峰一带的条最深,两端迅速变浅,这就是人跟着需求走的形状;而灰区全空,说明提前起班在数据上根本不划算——11:00 前上人,40 min 里有 8 min 是白付。

图 2 午高峰十个时段的订单量与需求骑手数
4.2 需求曲线 图 2 柱是每 12 min 的订单量,线是折算出的需求人数。曲线在 12:00—12:12 见顶(23.83 人)后回落,11:00 与 12:48 两头的需求只有峰值的三分之一。这条曲线是排班的原料:它越尖,40 min 的固定班次就越难贴合,缺口与富余必然同时出现。

图 3 均匀、按峰值与优化三种排班的成本与缺口对比
4.3 三方案对比 图 3 是核心对比。均匀排班 47 班、658 元、缺口 192.7 人·min;按峰值排班 72 班、1008 元、缺口为零;优化排班 52 班、728 元、缺口也为零。均匀排班比最优便宜 70 元(成本只有最优的 0.90 倍),代价是 192.7 人·min 缺口、约 96 单;按峰值排班比最优贵 280 元(1.38 倍),买到的是零缺口加 1008 人·min 空转。三个方案的成本最多差 53%,缺口却是从 0 到 192.7 人·min 的差别——第一目标只能是覆盖。

图 4 优化方案的逐时段在岗覆盖:缺口与富余
4.4 时序覆盖 图 4 把优化方案的在岗人数摊在时钟上,红色是缺口、蓝色是富余。整段没有红色,峰前 11:00—11:36 只留 0.2—0.5 人的薄边;蓝色全部压在 12:12 之后的回落段,最多处(12:24—12:36)多出 8.2 人。富余不是排错了,而是被 40 min 的长度逼出来的:12:20 起班的最后一批人一定要干到 13:00,而 13:00 前只要 8.67 人。班次越短,这段尾巴越短——这正是图 5 里短班次成本更低的来源。

图 5 班次长度对人力成本与班次数的影响
4.5 主灵敏度:班次长度 图 5 横轴 28—52 min,左轴成本、右轴班次数。七档长度全部能把缺口压到 0,右轴的台阶只在班次数上跳,成本从 28 min 的 715 元单调爬到 52 min 的 801 元:32、36、40 min 分别是 717、718、728 元,44 与 48 min 都是 739 元。短班次更省钱,因为它贴得住曲线(图 4 那条蓝色尾巴短了);但代价是班次数暴涨:28 min 要 73 个班,40 min 只要 52 个。省下的 13 元要用多排 21 个班来换,折 0.60 元一个班——一个班次的排班与交接成本只要超过 0.6 元,拉长到 40 min 就划算。

图 6 班次长度 × 骑手时薪 的人力成本热图
4.6 二维:长度 × 时薪 图 6 是 4 行 5 列的热图,行是时薪 16—31 元,列是班次长度。颜色自左下向右上单调变深,最贵的一格 1091 元;固定 40 min 这一列上下差 520 元,而固定时薪 21 元这一行从 32 min 到 48 min 只差 22 元。时薪是乘性因子,班次长度只通过班次数起作用,所以成本对工资的弹性远大于对长度的弹性——涨薪 5 元比换班次长度更疼。

图 7 订单量上浮对人力成本与班次数的影响
4.7 第三维:订单上浮 图 7 把订单量整体上浮到 1.30 倍。成本从 728 元升到 952 元(+31%),班次从 52 个增到 68 个(+31%),缺口全程保持为零。上浮 30% 就是人多 30%,一点规模效应都没有:起班档位固定,加人只能整班整班地加,成本随订单线性涨——离散档位收的是粒度税。

图 8 成本—缺口帕累托前沿与不可行区
4.8 帕累托前沿 图 8 用加权法把罚金从 2 扫到 32 元/(人·槽),得到成本 630—728 元、缺口 0.0—88.0 人·min 的前沿。左侧灰区是缺口小于 0,物理上不存在。均匀排班(方块)落在前沿右上方,被 λ=2 的加权解(630 元、缺口 88.0 人·min)完全支配:又贵 28 元、又多 104.7 人·min 缺口。前沿在缺口接近 0 处急剧变陡:末端的边际价码 5.3 元/(人·min),是远端 0.6 元的 8 倍——最后 5 人·min 的缺口要用 28 元去买,这就是最后一公里覆盖的价码。

图 9 逐槽覆盖偏差的累计分布(四种方案)
4.9 偏差分布 图 9 是逐槽覆盖偏差(在岗减需求取绝对值)的累计分布。优化排班 57% 的槽偏差不超过 0.35 人、平均 1.73 人,最大 8.17 人全部来自 12:20 那批人干到 13:00 的尾巴;低缺口罚版本(630 元、45 班)平均 0.87 人、最大 3.67 人,缺口 88.0 人·min 就是这么攒出来的;均匀排班 77% 的槽偏差超过 1 人、平均 3.28 人;按峰值排班最离谱,平均 8.40 人、偏差从 0.17 人一路铺到 16.33 人,整条曲线右移到图的右端。偏差的形状比总量更能说明问题:优化排班是贴着需求走,均匀排班是到处差一点,按峰值是处处多一大截。

图 10 优化方案中各起班点的边际贡献与配置人数
4.10 边际贡献 图 10 是逐档位的边际贡献:拿掉该档 1 人,缺口增加多少。尖峰一侧的档位边际贡献最高(29.3 人·min,起班 11:24),两端只有 2.7 人·min。没有一档是 0——这正是成本最小的证据:如果某个人可以被无声地拿掉而缺口不变,那就不是最优解。边际贡献的排序就是排班表的重要性排序,站点临时缺人时应该从右边开始减。
5 结论
- 最优排班 52 个 40 min 班次、728 元,缺口 0.0 人·min、满足率 100.0%;在岗 2080 人·min 对需求 1872 人·min,富余仅 208 人·min。
- 均匀排班 658 元,比最优便宜 70 元(0.90 倍成本),缺口却有 192.7 人·min、满足率只有 89.7%,折合约 96 单。它的总运力 1880 人·min 其实已经超过需求的 1872 人·min——缺口不是运力不够,是运力没落在对的时间上。
- 按峰值排班 1008 元(最优的 1.38 倍),缺口为零但富余 1008 人·min,等于用 280 元买永远按最忙那 12 min 配人的保险。峰值配人是保险,不是排班。
- 班次长度:28—52 min 七档都能做到零缺口,成本从 715 元单调升到 801 元,40 min 为 728 元。短班次省的是尖峰前后的溢出,付的是班次数——28 min 比 40 min 便宜 13 元,却要多排 21 个班(73 对 52)。40 min 不是成本最低点,而是"每班省 0.6 元管理成本"这条线之上的折中;若站点管得动 73 个班,切短更省。
- 成本对时薪的弹性远大于对班次长度:时薪 16→31 元,固定 40 min 一列成本差 520 元;订单上浮 30% 则成本涨 31%、班次涨 31%,缺口仍为零。反过来,去掉缺口罚金(630 元、45 班)缺口立刻涨到 88.0 人·min——缺口最小和成本最低在离散档位下不可兼得,必须先声明谁优先,再谈排班。
参考文献
[1] Gurobi Optimization LLC. Gurobi Optimizer Reference Manual[Z]. 2024.
[2] Kellerer H, Pferschy U, Pisinger D. Knapsack Problems[M]. Berlin: Springer, 2004.
[3] Martello S, Toth P. Knapsack Problems: Algorithms and Computer Implementations[M]. Chichester: John Wiley & Sons, 1990.
[4] 司守奎, 孙玺菁. 数学建模算法与应用[M]. 3 版. 北京: 国防工业出版社, 2021.

浙公网安备 33010602011771号