📈 第3章 最优化
📄 一句话概括

客户要求每天至少 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 秒实验

  1. 把保底从 130 改成 145,还可行吗?为什么?
  2. 把保底改成 100(比原最优低),利润会变吗?
  3. 试着在 Excel 里摆一遍这个表(本沙箱没有 Excel,画在纸上也算数)。

常见误区

  1. 以为加约束不影响最优:约束只要切到最优解,利润一定下降。没切到才不影响。
  2. Solver 跑完不核对:不代回检查,很容易漏掉一条约束,得到一个假最优。
  3. 把「保底」当免费:任何限制都有代价,代价就是「没有它你能多赚多少」。
  4. 目标单元格选错:选成某个约束行,求出来的「最大」毫无意义。
自测三问(点开对答案) 1. 加一条约束,利润为什么只会降不会升? —— 因为可行区域只会变小。原来的最优解如果还在区域里,它仍然可选;如果被切掉,只能选更差的。 2. 这 90(百元)该怎么向客户解释? —— 这是「保底 130」这个承诺的价格:每天少赚 9000 元,客户溢价必须超过它才划算。 3. 用 Solver 之前要准备什么? —— 三行字:可变单元格、目标单元格、约束。表格只是这三行字的排版方式。

模型能算了,可它的答案只对「你写进去的价格」有效。 原料一涨价,最优解就换地方——下一篇看它怎么把利润算成负数。

相关笔记