客户要求每天至少 130 百只鸭,这条约束一加,可行区域被切掉一块,利润从 555 掉到 465。 掉的那 90(百元)就是这条约束的代价,也就是「接这张单要收多少钱才不亏」。 电子表格和 Solver 都在做同一件事:把你的三行字抄进格子,再替你枚举顶点。
上一篇求出了无约束的最优。这一篇加一条新约束,看区域怎么变、利润怎么掉。 「一条约束值多少钱」这个概念,在第 11 章会以「误差的代价」的形式再出现一次。
一、新约束切掉了什么
x ≥ 130 是一条竖线,它把可行区域左边那块全切掉。 原来的最优解 (110, 45) 正好落在被切掉的那边——它不再可行了。 新的最优解挤到 (130, 15):鸭子被钉在 130,鱼只能做 15。
| 鸭子 | 鱼 | 利润 | |
|---|---|---|---|
| 原最优 | 110 | 45 | 555 |
| 新最优 | 130 | 15 | 465 |
| 差 | +20 | −30 | −90 |
二、这 90 意味着什么
它意味着:接这张保底 130 的单,每天少赚 9000 元。 所以要不要接,取决于客户愿意为「保底」付多少钱。低于 9000 元/天,这笔生意在经济上不划算。 注意这不是「客户压价」,是「约束的成本」。任何让你必须做某件事的要求,都有这个成本。
三、电子表格怎么摆
Excel 的摆法有固定套路:一列放变量,一列放系数,一行放约束。
A1: 鸭子 B1: 110 C1: <-- 可变单元格
A2: 鱼 B2: 45 C2: <-- 可变单元格
A4: 利润 B4: =3*B1+5*B2 C4: <-- 目标单元格(最大)
A6: 注塑 B6: =2*B1+4*B2 C6: <= 400
A7: 装配 B7: =3*B1+2*B2 C7: <= 420
A8: 原料 B8: =B1+B2 C8: <= 180
A9: 保底 B9: =B1 C9: >= 130
Solver 要填三样东西:目标单元格(B4,求最大)、可变单元格(B1:B2)、约束(C6:C9)。 点「求解」,它内部做的就是上一篇那段顶点枚举——只是它不会把过程给你看。
四、Solver 不会替你检查模型
你把约束写漏一条,它照样给你一个答案,而且答得很干脆。 所以跑完之后必须自己核两件事:答案代回去满足所有约束吗?哪几条约束用满了? 这两件事花不了一分钟,能挡掉九成的「Solver 算出来是负的」这类事故。
五、电子表格的边界
两三个变量、几条约束,表格很舒服:改一个数,图上立刻能看到。 变量上到几十个,表格就看不清了,得换成脚本或专门的求解器。 但不管用什么工具,三行字的写法不变:变量、目标、约束。
六、动手 Demo:加一个新约束,区域只会变小
在原模型上加一条 x ≥ 130,重算最优解与利润差。 最后那行「溢价超过多少才值得接」,就是这条约束的定价。
A = [(2, 4), (3, 2), (1, 1), (-1, 0), (0, -1), (-1, 0)]
B = [400, 420, 180, 0, 0, -130]
c = (3, 5)
pts = []
for i in range(len(A)):
for j in range(i + 1, len(A)):
a1, a2 = A[i], A[j]
det = a1[0] * a2[1] - a1[1] * a2[0]
if abs(det) < 1e-12:
continue
x = (B[i] * a2[1] - a1[1] * B[j]) / det
y = (a1[0] * B[j] - B[i] * a2[0]) / det
if x < -1e-9 or y < -1e-9:
continue
if all(A[k][0] * x + A[k][1] * y <= B[k] + 1e-9 for k in range(len(A))):
pts.append((x, y))
bx, by = max(pts, key=lambda p: c[0] * p[0] + c[1] * p[1])
print('新最优 (%.1f, %.1f) 利润 %.1f' % (bx, by, c[0] * bx + c[1] * by))
print('比原来少 90.0,也就是每天 %.0f 元' % (90 * 100))
print('客户溢价超过这个数才值得接')
实跑输出:
--------------------------------------------------------------------
加一个新约束,区域只会变小
--------------------------------------------------------------------
① 客户提了一个要求
--------------------------------------------------------------------
老客户说:每天至少要给我 130 百只鸭子,不然我换供应商。
这条要求翻译成约束就是:x ≥ 130。
② 区域怎么变
--------------------------------------------------------------------
新约束加进来,交点 14 个,可行 3 个。
旧最优:x = 110.0,y = 45.0,利润 555.0
新最优:x = 130.0,y = 15.0,利润 465.0
利润少了 90.0(百元),等于 9000 元。
③ 这就是约束的代价
--------------------------------------------------------------------
每一条约束都会切掉一块可行区域。切掉的越多,能拿到的利润越少。
反过来看:如果这个客户给的溢价超过 9000 元/天,这笔生意就值得接。
④ 约束也可以松
--------------------------------------------------------------------
如果是自己厂里的规矩太死(比如「装配线一天只能开 7 小时」),松掉它就能多赚。
松约束值多少钱,就用「松一点能多赚多少」来算——这叫影子价格。
注意:只有用满的约束松了才有用。没用满的那条(原料),松了也不多赚一分。
新最优 (130.0, 15.0),利润 465.0。原最优 555.0,差 90.0。 这 90 不是「成本上升」,是「选择变少」——同样的机器、同样的原料,只是不能那样配了。
30 秒实验
- 把保底从 130 改成 145,还可行吗?为什么?
- 把保底改成 100(比原最优低),利润会变吗?
- 试着在 Excel 里摆一遍这个表(本沙箱没有 Excel,画在纸上也算数)。
常见误区
- 以为加约束不影响最优:约束只要切到最优解,利润一定下降。没切到才不影响。
- Solver 跑完不核对:不代回检查,很容易漏掉一条约束,得到一个假最优。
- 把「保底」当免费:任何限制都有代价,代价就是「没有它你能多赚多少」。
- 目标单元格选错:选成某个约束行,求出来的「最大」毫无意义。
自测三问(点开对答案)
1. 加一条约束,利润为什么只会降不会升? —— 因为可行区域只会变小。原来的最优解如果还在区域里,它仍然可选;如果被切掉,只能选更差的。 2. 这 90(百元)该怎么向客户解释? —— 这是「保底 130」这个承诺的价格:每天少赚 9000 元,客户溢价必须超过它才划算。 3. 用 Solver 之前要准备什么? —— 三行字:可变单元格、目标单元格、约束。表格只是这三行字的排版方式。模型能算了,可它的答案只对「你写进去的价格」有效。 原料一涨价,最优解就换地方——下一篇看它怎么把利润算成负数。
- ☐ (本篇完,下一篇 3.5 Solver 与跌穿地板的利润)