这一章要解决什么
小林在期末前第 4 周给自己排晚自习。他给自己定的规矩只有两条:一周要坐满 20 小时;每晚不超过 2 小时,超过就睡不够。周一到周五能排,周末回家,所以一周只有 5 个晚上。两条规矩加"只有五天"这一个事实,写成 12 行约束,按下运行,屏幕上出现的不是排法,而是一行字:Model is infeasible。
没有解、没有目标值、没有节点,日志里也没有"哪一行有问题"。工具不崩、不报语法错、不提示行号,它只是很干脆地说"这题无解"。一个刚学建模的人在这种时候最容易做的,是从第一条约束开始一条一条放宽,解一次看一次,直到某一版突然跑通,然后把那一版当成答案。小林的同事小唐就是这么干的,试了 10 次,第 10 次跑通了,报出"每周 10 小时、疲劳代价 16.0 点",还很得意地说省了 6.6 点。
这一例要回答三件事:这套排程一周到底能排多少小时、差在哪两条规则上;用最小冲突集(IIS,把模型逐行删到"再删一条就有解"的那一组行)怎么在 0.6 毫秒里把范围从 12 行缩到 6 行;以及三种改法各自要付多少代价,先改哪一条。
约束有三条线:一周自习总量不能低于 20 小时;每晚不能超过 2 小时;一周至少 3 个晚上有自习(怕自己连着几天不动笔)。前两条是硬的,第三条是用来防懒的。目标是把"挤掉睡眠与休息的代价"压到最小,每天每小时的代价不一样,越往后越贵。目标只想决定"同样的小时数往哪几天放",它管不了可行域是不是空的。
模型不替你决定什么:每小时 1.0 到 2.4 点这些数是小林自己打的,它只排序、不带单位换算的权威性;改一列数,"往哪几天放"就会换。更要紧的是"一周 20 小时"这个数根本不在模型里——它是小林拍的目标,模型只负责告诉你这个目标与"每晚 2 小时 × 5 天"能不能同时成立。目标合不合理、要不要为了它牺牲睡眠,这两件事模型一个字都不说。
11.1 能动的只有"每晚几小时",动不了"一周有几天"
七天摊在表 11-1 里:五个工作日、两个周末,代价一列、上限一列。模型里只有五个决策变量,因为"周末回家"这句话不是约束,是把周六周日两天从变量表里删掉了。这件事在后面很关键:IIS 只报"哪几行打架",它看不见"你把两天从变量表里划掉了"。
表 11-1 七天里能用的五天:每晚的自律上限与疲劳代价
| 代号 | 星期 | 疲劳代价(点/小时) | 自律上限(小时/晚) | 在默认排程里 |
|---|---|---|---|---|
| MON | 周一 | 1.0 | 2 | 在 |
| TUE | 周二 | 1.2 | 2 | 在 |
| WED | 周三 | 1.5 | 2 | 在 |
| THU | 周四 | 1.9 | 2 | 在 |
| FRI | 周五 | 2.4 | 2 | 在 |
| SAT | 周六 | 0.9 | 6 | 不在 |
| SUN | 周日 | 1.0 | 6 | 不在 |
表注:代价是"挤掉睡眠与休息"的折算点数,越靠近周末越贵;周末两天不累,所以点数低,上限也放到 6 小时(白天能自习)。默认排程只含前五天,一周容量 5 × 2 = 10 小时。
能决定的是每晚几小时:
hd ≥ 0,d∈{周一,…,周五}h_d\ \ge\ 0,\qquad d\in\{\text{周一},\dots,\text{周五}\}hd≥0,d∈{周一,…,周五}
能排的五天与各自的代价如图 11-1 所示。hdh_dhd是第ddd晚自习几小时,单位是小时/晚。另有一组 0-1 变量xdx_dxd,取 1 表示这一晚有自习,它不是排法的一部分,只是为了让"一周至少 3 个晚上"这句话能用线性式子写出来。
图 11-1 左:七天的每小时代价与每晚上限;右:默认排程的账面容量与每周 20 小时的差
这张图告诉你什么:左图里深色五根是能排的晚上,浅色两根是周末——周末那两根最便宜(0.9 与 1.0),却不在默认排程里,这个"便宜但用不上"的错位是后面所有补救办法的入口。右图把两句话叠在一根柱子上:底下 10 小时是 5 个晚上乘 2 小时排得下的,上面斜纹那 10 小时是排不下的。不用求解器也能看出来 10 小于 20,可这句话在建模时没人真的乘过一遍;屏幕上那一行
Model is infeasible只是把一次漏算变成了一次失败。
11.2 三条线里两条在打架,写的时候没人算过 2×5
第一条是总量:
∑dhd ≥ 20(小时/周)\sum_{d} h_d\ \ge\ 20\qquad(\text{小时/周})d∑hd≥20(小时/周)
第二条是每晚自律:
hd ≤ 2,d∈{周一,…,周五}(小时/晚)h_d\ \le\ 2,\qquad d\in\{\text{周一},\dots,\text{周五}\}\qquad(\text{小时/晚})hd≤2,d∈{周一,…,周五}(小时/晚)
五条同构的小行,一天一条。第三条把"开没开"与"开多久"接上,再要求一周至少 3 个晚上:
hd ≤ 24 xd,∑dxd ≥ 3,xd∈{0,1}h_d\ \le\ 24\,x_d,\qquad \sum_d x_d\ \ge\ 3,\qquad x_d\in\{0,1\}hd≤24xd,d∑xd≥3,xd∈{0,1}
这里的 24 是一天的物理上界,不是规矩,它只保证"没开的晚上hdh_dhd必须等于 0"。三条线一共 12 行:1 条总量、5 条每晚、5 条联动、1 条次数。
问题出在前两条之间。第一条要 20 小时,第二条把每晚钉在 2 小时,五个晚上加起来只有 10 小时。两条各自都很合理,放在一起就是空集。这种错在写的时候看不出来,因为两句规矩是分开写的,一句在讲目标,一句在讲作息,谁也没在中间乘一次 2 乘 5。
11.3 目标管"往哪几天放",管不了"有没有解"
min ∑dcd hd\min\ \sum_{d} c_d\,h_dmind∑cdhd
cdc_dcd是表 11-1 第三列,单位是点/小时,所以目标值的单位是点/周。星期越靠后越贵:周一 1.0,周五 2.4,周末两天 0.9 与 1.0。目标只做一件事——在小时数固定时,把小时往便宜的日子推。
把七天按每小时代价从低到高排好,一档一档往上填,就得到如图 11-2 所示的那条折线。
图 11-2 按代价从低到高填满七天:累计小时与累计代价,以及两条参照线
这张图告诉你什么:折线的斜率就是当前那一档的代价,填到第 6 格(周六 6 小时、周一 2 小时、周日 6 小时、周二 2、周三 2、周四 2)刚好 20 小时,累计 22.6 点。虚线是"每小时都只花 0.9 点"的理想下界,20 小时对应 18.0 点——那个解要周六一天塞进 20 小时,每晚 2 小时的线一句话就把它否了,所以它只能当尺子。22.6 与 18.0 之间的 4.6 点,全是"每晚 2 小时"这条线一点点磨出来的;右边那张填充顺序表还说明另一件事:7 天里只用了 6 天,周五(2.4 点)一次没排。
目标在这里有个容易搞混的地方:把代价系数全部改成 0,模型仍然不可行。目标值再小也换不来一个解,因为可行域是空的——空集上的最小值没有定义。这就是为什么"解不出来"必须回到约束那一边查,而不是去调目标。
11.4 十次试放才跑通的那一版,把要求改成了答案
整道题:
min ∑dcd hds.t. ∑dhd≥20hd≤2,d∈{周一,…,周五}hd≤24 xd,∑dxd≥3,xd∈{0,1}\begin{aligned} \min\ & \sum_{d} c_d\,h_d\\ \text{s.t.}\ & \sum_{d} h_d \ge 20\\ & h_d \le 2, && d\in\{\text{周一},\dots,\text{周五}\}\\ & h_d \le 24\,x_d,\quad \sum_d x_d \ge 3, && x_d\in\{0,1\} \end{aligned}mins.t.d∑cdhdd∑hd≥20hd≤2,hd≤24xd,d∑xd≥3,d∈{周一,…,周五}xd∈{0,1}
真正跑起来只有一个build(),三个参数决定它是哪一版:cap_wd(每晚上限)、weekend(周末进不进变量表)、lb(周下限)。
小唐的第一版没有 IIS,用的是最土的办法:一条一条地放宽 2 小时,解一次看一次。改哪一条按随机顺序来,种子固定,十次试放的顺序与每次的缺口都打在终端里(下面摘录,中间若干行删去);缺口本身汇总在表 11-2 与图 11-3。前九次全部INFEASIBLE:五次把某一晚的上限从 2 抬到 4,容量从 10 涨到 12,离 20 还差 8;四次把周下限从 20 降到 18、16、14、12,容量只有 10,还是够不着。第 10 次把下限砍到 10,模型跑通了,报出每周 10 小时、疲劳代价 16.0 点。
==================================================================================================== 求解失败:不可行怎么查 —— 小林,2026 年 11 月,期末前第 4 周 模型:10 个决策变量(5 个 h + 5 个 x),12 行约束 ---------------------------------------------------------------------------------------------------- 第一次求解:INFEASIBLE,0.32 毫秒,12 行 10 列 没有解、没有目标值、没有节点:状态一栏只有 INFEASIBLE,目标值、下界、间隙三栏都是空的 Gurobi 日志里说到不可行的全部句子: Optimize a model with 12 rows, 10 columns and 25 nonzeros (Min) Presolve removed 11 rows and 5 columns Explored 0 nodes (0 simplex iterations) in 0.00 seconds (0.00 work units) Solution count 0 Model is infeasible Best objective -, best bound -, gap - ---------------------------------------------------------------------------------------------------- 最小冲突集(IIS):12 行里只留 6 行,其余 6 行被排除 IIS 计算 0.60 毫秒;留下的行:week_lb、cap_MON、cap_TUE、cap_WED、cap_THU、cap_FRI 归并同构行 → 2 条互斥规则: 每周自习下限 20 小时(1 行) 每晚自律上限 2 小时 × 5 个晚上 = 10 小时(5 行) 单行删除试验:12 行逐条删掉再解 week_lb OPTIMAL 删它就能解 → 它在冲突里 cap_MON OPTIMAL 删它就能解 → 它在冲突里 ...(cap_TUE、cap_WED、cap_THU、cap_FRI 同) link_MON INFEASIBLE 删它还是不可行 → 陪跑 ...(link 五行与 nights_lb 同) ---------------------------------------------------------------------------------------------------- 第一版:一条一条放宽 2 小时再试(随机顺序,种子 157) 第几次 动了哪一条 下限 容量 差 结果 1 week_lb 下限 20 → 12 小时/周 12 小时 10 小时 2 INFEASIBLE 2 cap_FRI 上限 2 → 4 小时/晚 20 小时 12 小时 8 INFEASIBLE 3 week_lb 下限 20 → 16 小时/周 16 小时 10 小时 6 INFEASIBLE ... 9 week_lb 下限 20 → 14 小时/周 14 小时 10 小时 4 INFEASIBLE 10 week_lb 下限 20 → 10 小时/周 10 小时 10 小时 0 OPTIMAL 16.0 点 / 10 小时 第 10 次才跑通;他把结论写成「下限写错了,改成 10 就行」 报出:每周 10 小时,疲劳代价 16.0 点——目标被砍掉 10 小时(50%) ---------------------------------------------------------------------------------------------------- 三种修法(各自只动一处) 甲 加周末两天:周六周日各 6 小时上限 OPTIMAL 22.6 点 每周 20 小时 每小时 1.13 点 乙 上限 2 → 4 小时/晚(物理天花板) OPTIMAL 32.0 点 每周 20 小时 每小时 1.60 点 丙 下限 20 → 10 小时/周 OPTIMAL 16.0 点 每周 10 小时 每小时 1.60 点 理想下界:18.0 点(20 小时全按最便宜的 0.9 点/小时,不计每晚上限与天数) 比理想下界多付:甲 +4.6 点(+25.6%)、乙 +14.0 点(+77.8%) ====================================================================================================表 11-2 四个版本:动了哪一条、实到多少小时、付多少代价
| 版本 | 动了哪一条(建模.py 行号) | 每周实到(小时) | 疲劳代价(点/周) | 每小时代价(点/小时) | 比理想下界(点) | 结论 |
|---|---|---|---|---|---|---|
| 第一版,放宽 10 次后跑通 | week_lb改值 20 → 10(第 110 行) | 10.0 | 16.0 | 1.60 | −2.0 | 错:改的是要求 |
| 修法甲,加周末两天 | days加两天(第 102 行) | 20.0 | 22.6 | 1.13 | +4.6 | 对,且最省 |
| 修法乙,上限 2 → 4 | cap(第 116 行) | 20.0 | 32.0 | 1.60 | +14.0 | 对,比甲多付 9.4 |
| 修法丙,下限 20 → 10 | week_lb改值(第 110 行) | 10.0 | 16.0 | 1.60 | −2.0 | 知情选择,代价是少 10 小时 |
表注:"比理想下界"是该版目标值减 18.0;第一版与修法丙解出同一组数(每晚 2 小时、5 晚、16.0 点),差别只在知不知道自己在改什么。每小时代价 = 目标值 ÷ 每周实到小时。
图 11-3 十次试放:柱高是"下限减容量",只有第 10 次归零
这张图告诉你什么:前九根柱子最高的到了 8 小时,意思是改了之后模型还差 8 小时——每次只动一条、只动 2 小时,加起来也补不上 10 小时的缺口,这就是九次全白试的原因。第 10 根柱子的高度是 0,看上去像"终于成功了",其实是把要求从 20 砍成 10,让下限掉到容量下面去。图上唯一的绿色标记不是"正确",是"可行";可行与正确之间差着 10 小时。
第一版错在哪一步,值得说清楚三件事。
**①错在改错了对象。**两条打架的线是"要 20 小时"和"每晚只能 2 小时",改哪一条都能让模型跑起来,但改第一条等于把目标砍掉一半,改第二条才是"多挤时间"。他把 20 当成笔误,改成了 10。同一个 10 小时,作为"知情选择"是修法丙,作为"排查手段"就是第一版——数字一样,性质相反。
**②为什么前九次都白试。**容量只有 10 小时,每一次单点放宽 2 小时,要么把某一晚的上限抬到 4(容量 12),要么把下限降到 12 以上,容量这一头始终只有 10 或 12,缺口一次也没补上。要在不改要求的前提下补上 10 小时,必须动"天数"或"每晚上限",而这两样都不在他那十次尝试的名单里:名单里每一次只碰一条、只碰 2 小时。方法本身决定了它找不到答案,不是运气差。
**③差多少。**报 10 小时,实际要 20 小时,少 10 小时、正好一半。代价那一栏更迷惑人:16.0 点比修法甲的 22.6 点低 6.6 点,看上去"省了 6.6 点",其实是用一半的学习量换来的。按每小时算,第一版 1.60 点/小时,修法甲 1.13 点/小时——少做题的那一版每小时还更贵,因为它被迫把 10 小时摊在五天(含最贵的周五)上,一天不落。
11.5 Gurobi 在预求解里就判了不可行,一个节点都没走
模型的规模是 12 行、10 列(5 个连续变量 + 5 个 0-1 变量)。日志里与这次失败有关的句子只有五行:
Presolve removed 11 rows and 5 columns、Explored 0 nodes (0 simplex iterations)、Solution count 0、Model is infeasible、Best objective -, best bound -, gap -。
预求解先砍掉 11 行、5 列,剩下的东西不足以自洽,于是直接判死。节点 0、迭代 0、间隙这一栏是个横杠——11.5 这一节本来该讲"节点多少个、间隙多紧",可这道题根本没有分枝过程可讲。这正是不可行诊断与"求解太慢"最不一样的地方:慢的时候日志满是节点和界,不可行的时候日志只有一句结论,中间 11 次消元全在求解器肚子里。
再算一次 IIS,耗时 0.60 毫秒,把 12 行缩到 6 行。修法甲是另一回事:7 个hhh加 7 个xxx,16 行 14 列,日志写Presolve removed 16 rows and 14 columns,也是 0 个节点、0 次迭代,目标 22.6,耗时见表 11-3。
两个模型都在预求解里被解决干净,节点数都是 0;间隙只在修法甲那一行有定义,0.0000,不可行那一行是横杠。所以这里要改掉一个习惯:节点数、间隙这些过程量,只在"模型有解但难解"时才有信息量。模型不可行的时候,它们一个都读不出来,能读的只有 IIS。
11.6 COPT 也说不可行,两家一致不等于题目对
同一份模型镜像给 COPT,跑两个对象:原来那个不可行的,和修法甲,四行结果见表 11-3。
表 11-3 两个引擎 × 两个模型:状态、目标值、节点、间隙、耗时
| 模型 | 引擎 | 状态 | 目标值(点/周) | 搜索节点 | 间隙 | 耗时(毫秒) |
|---|---|---|---|---|---|---|
| 不可行模型 | Gurobi | INFEASIBLE | — | — | — | 0.20 |
| 不可行模型 | COPT | INFEASIBLE | — | — | — | 0.10 |
| 修法甲 | Gurobi | OPTIMAL | 22.6 | 0 | 0.0000 | 0.20 |
| 修法甲 | COPT | OPTIMAL | 22.6 | — | — | 0.40 |
表注:修法甲那一行的相对偏差是 0.0e+00,不可行两行的目标值为空、偏差无从算起;COPT 侧没有单独取节点与间隙,记"—"。耗时是同一次运行里三次的中位数;带环境初始化的第一次调用会到几毫秒,表里没取那一次。
四格里两两相同。这排除的是"某个引擎把模型读错了":换库、换算法、换默认参数,不可行还是不可行,22.6 还是 22.6。它排除不了另一半——两家都说不可行的时候,也一样一个字都不解释。求解器的职责是判定,不是解释;解释要人去要,要的办法就是 IIS。双引擎对账能证明"两张引擎读的是同一道题",证明不了"这道题该这么出"。
11.7 IIS 只留 6 行,5 行是同一条规则的 5 个副本
IIS 的结果如图 11-4 所示:六行是week_lb与cap_MON…cap_FRI。被排掉的六行是五行link_*与nights_lb——它们不是"没问题",是"删掉也不解决问题",所以不在最小冲突集里。
图 11-4 12 行里哪 6 行进了冲突集,哪 6 行只是陪跑
这张图告诉你什么:红色六行连在一起,灰色六行整块被排除。关键在那五条同构的
cap_*:模型把"每晚 2 小时"写成一天一行,IIS 就老老实实报五行;人读的时候要把它们合成一行"每晚 2 小时 × 5 天 = 10 小时",冲突才是"20 小时 ≥"对"10 小时 ≤"这 2 条规则。IIS 数的是行,不是规则——这是一句必须自己补上的翻译。
自己补翻译这件事,靠一次单行删除试验就能验证:12 行逐条删掉再解,删week_lb或删任一条cap_*都能跑通,删link_*或nights_lb仍然INFEASIBLE。六次变有解、六次照旧,与 IIS 的结论一字不差。这一步值得每次做,因为它把 IIS 的"最小"落成了 12 个可以数的事实,而不是一个需要相信的结论。
还有两件事 IIS 不会告诉你。
**一是"5 天"这个数不在行里。**它写在变量表上:days = WD + (WE if weekend else [])(第 102 行)。IIS 说"每晚 2 小时",但你得自己知道这个"每晚"是五天还是七天——同一份模型把周末放进变量表,冲突立刻消失(那就是修法甲)。诊断结论要配着变量定义一起读,否则会得出"每晚 2 小时太紧"这种半对半错的话。
**二是"能删"不等于"该删"。**六行里删任何一行都能让模型跑起来,代价却差着一个量级:删cap_*是每晚从 2 小时往 4 小时抬,人要多付 9.4 点;删week_lb是把 20 小时降到 10 小时,模型跑得漂亮,题已经换了。IIS 只给"可删的清单",排序要靠目标值与人的取舍,这一步没有求解器能代劳。
11.8 下限扫到 10 小时还可解,11 小时就翻脸
把"每周自习下限"当成一个可以调的参数,从 8 小时扫到 24 小时,每档解一次,其余不变(每晚 2 小时、只排五天),结果见表 11-4 与图 11-5。
表 11-4 每周自习下限从 8 扫到 24 小时:可行性在哪一档翻转
| 周下限(小时/周) | 状态 | 最优目标(点/周) | 每周实到(小时) |
|---|---|---|---|
| 8 | OPTIMAL | 11.2 | 8 |
| 9 | OPTIMAL | 13.6 | 9 |
| 10 | OPTIMAL | 16.0 | 10 |
| 11 | INFEASIBLE | — | — |
| 12 | INFEASIBLE | — | — |
| 13 | INFEASIBLE | — | — |
| 14 | INFEASIBLE | — | — |
| 15 | INFEASIBLE | — | — |
| 16 | INFEASIBLE | — | — |
| 17 | INFEASIBLE | — | — |
| 18 | INFEASIBLE | — | — |
| 19 | INFEASIBLE | — | — |
| 20 | INFEASIBLE | — | — |
| 21 | INFEASIBLE | — | — |
| 22 | INFEASIBLE | — | — |
| 23 | INFEASIBLE | — | — |
| 24 | INFEASIBLE | — | — |
表注:17 档里有 3 档可解、14 档不可解;可解的三档,目标值随下限上升,每小时 1.4 点、1.51 点、1.60 点,越往后越贵,因为便宜的几天先被填满。
图 11-5 上:目标值只在下限 ≤ 10 小时时有定义;下:实到小时数贴着容量走
这张图告诉你什么:上半幅那条线到 10 小时就断了,右边 14 个取值连点都画不出来——"没有目标值"不是目标值等于 0,是这一栏压根不存在。下半幅更直白:实到小时数等于下限本身(8、9、10),一条 45 度线爬到容量 10 就停住。两条线合起来给出同一个临界值:10 小时 = 每晚 2 小时 × 5 天。这个数不用扫也能算,扫一遍是为了让"临界"这件事变成一个可以看见的断点,而不是脑子里的一句"大概不够"。
临界值有解析式:容量等于每晚上限乘天数,2×5=102\times 5=102×5=10小时/周。下限不超过 10 小时,模型就有解;一过 10,立刻不可行。这也解释了第一版为什么必须在第 10 次才碰对:能跑通的下限不是 18、16、14、12,只有 10 及以下。
有了临界值和三种修法,修复顺序就排得出来了。三种改法各自只动一处,代价放在同一把尺子上比,如图 11-6 所示。
图 11-6 三种修法与第一版落在"小时—代价"平面上的什么位置
这张图告诉你什么:甲与乙都在 20 小时这条竖线上,一个 22.6 点、一个 32.0 点,同样做完 20 小时,差 9.4 点(41.6%)——差别不在目标,在改的是"天数"还是"每晚上限"。丙与第一版都在 10 小时、16.0 点那个位置,是同一组数的两种说法。空心菱形是理想下界 18.0,谁都到不了,因为到它那里要把 20 小时塞进一天。往右下走是"同样小时、更低代价",这张图上没有第二个点比甲更靠右下:要保住 20 小时,甲是这几个点里最省的一个。
按代价从小到大排,修复顺序是这样的:
- 先加天数(甲):周末两天各 6 小时上限,一周容量从 10 涨到 22 小时。目标 22.6 点,比理想下界多付 4.6 点(25.6%),比乙少付 9.4 点。代价落在"周末不能回家"上,这是唯一不动任何一条自律线的改法。
- 再加每晚上限(乙):把 2 小时抬到 4 小时(19:30 到 23:30,宿舍熄灯前最多就这么多)。目标 32.0 点,比甲多付 9.4 点(41.6%)。这一档没有余地:5 晚乘 4 小时正好 20,一晚都不能少,排法被锁死成每晚 4 小时。
- 降要求(丙)放在末位:20 小时降到 10 小时,目标 16.0 点。它不是"修法",是把题改了;只有当甲、乙都不可能(周末回不了家,又熬不到 23:30)时,才轮到它,而且必须明说"这学期每周 10 小时"。
11.9 小结
- 这套排程一周只能排 10 小时(5 晚 × 2 小时),要 20 小时,差 10 小时。求解器给的是一行
Model is infeasible,12 行、10 列、0 节点、0 迭代,没有目标值也没有间隙。 - 最小冲突集 0.60 毫秒算完,把 12 行缩到 6 行:1 条周下限 + 5 条每晚上限,归并同构行后就是 2 条互斥规则。单行删除试验 12 次,6 次变有解、6 次照旧,与 IIS 完全对上。
- 第一版随手放宽 10 次,第 10 次才跑通,报 10 小时 / 16.0 点,少 10 小时(50%),每小时还更贵(1.60 对 1.13)。同样的问题用 IIS 查,一次求解加一次诊断就到 2 条规则。
带走三问。
- **你的模型不可行时,先看的应该是哪几行?**不是对着 12 行从头怀疑,也不是一条一条放宽——一条一条放宽能跑通,只能说明"删掉某样东西就能解",说明不了该删哪一样。
- **你能把 IIS 的 6 行读成几条规则?**五条
cap_*是同一句话的五个副本。读不出这层归并,就会得出"每晚 2 小时太紧"的结论;读出这层,才知道真正的矛盾是"总量 20"对"容量 10"。 - **你的"修复"是修还是改题?**甲和乙是修,丙是把 20 改成 10。同一个 16.0 点,作为知情选择是决策,作为排查结果是错误。
三句"什么时候别用"。
一是别把 IIS 当选最省改法的工具。它只保证这一组行删一条就有解,不排序、不算代价;本例六行里删cap_*与删week_lb都能跑起来,代价差 9.4 点外加 10 小时的学习量。
二是别在变量表被砍过之后只读 IIS。“只允许 5 天"不在任何一行里,它在days的定义里;IIS 说"每晚 2 小时”,你得自己补上"五天"这个乘数,否则诊断结论会漏掉一半。
三是别用双引擎一致来顶替诊断。两家引擎对同一个不可行模型都给INFEASIBLE,对同一个可行模型都给 22.6,它们一致只能说明读题一致;为什么不可行、该改哪一条,还得回到 IIS 和目标值上。