対象読者:混合整数線形計画(MIP)の基礎(二値変数・線形不等式・目的関数)をご存じの方。
一見ほのぼのとした「柵でヒツジを囲うパズル」が、グラフ理論の最小点カット問題を土台にしつつ、どのように「穴ぼこ(負の重み)」や「孤立領域の排除(連結性)」を線形制約として定式化しているのかを解説します。
盤面は無向グリッドグラフ G = (V, E) として表せます。上下左右に隣接する通行可能なマスの組が辺集合 E を構成し、各頂点(マス)v ∈ V は以下の性質を持ちます。
| 盤面のタイル | 集合・記号 | 柵の設置 | 重み(得点) w_v |
数理モデル上の役割 |
|---|---|---|---|---|
| 🐑 ヒツジ | S ⊂ V |
不可 | +2 |
牧場の起点(湧き出し Source、常に reach_s = 1) |
| 🐺 オオカミ | W ⊂ V |
不可 | 0 |
侵入源(常に reach_w = 0 を強制) |
| 🌱 草地 / 🍓 野いちご | V_fence ⊂ V |
可能 | +1 / +5 |
正の利得を持つ通常頂点(柵を置くと通行不可) |
| 🕳️ 穴ぼこ | V_fence ⊂ V |
可能 | -5 |
負の重みを持つ頂点(牧場に入ると減点、真上に丸太の柵を打ち込んで塞げる) |
| 🌉 石橋 | V_bridge ⊂ V |
不可 | +1 |
通行できるがカット不能な頂点(カット容量 ∞) |
| 💧 水辺 / 🪨 岩 | V_wall |
不可 | — | 最初から通行不能(グラフ V から除外して扱える天然の壁) |
プレイヤーの目的は、最大 K 本までの柵を設置してすべてのオオカミ W からヒツジ S への経路を遮断し、その状態でヒツジ S から歩いて到達できる連結領域(まきば)の合計スコアを最大化することです。
まずはスコア(いちごや穴ぼこ)を一旦忘れ、「最小何本の柵でオオカミ W とヒツジ S を分断できるか?」という部分問題だけを取り出してみます。
通常の最小カット問題はグラフの「辺(Edge)」を切りますが、このパズルでは「マス(頂点 Vertex)」の上に柵を置いて通行止めにするため、グラフ理論における最小 s-t 点カット問題(Minimum Vertex Cut)に対応します。
W からヒツジ S へ至る頂点素な(互いに柵設置可能マスを共有しない)経路の最大本数は、両者を分断するのに必要な柵の最小本数に等しい」という定理が成り立ちます。🌉(カット不能頂点)を介してオオカミの侵入路が3本に分岐していれば、オオカミの目の前を1本の柵で塞ぐことは原理的に不可能になります。
このパズルが最小カット問題より一段深く、かつ面白い理由は次の2点にあります。
K 本の予算制約下での領域価値最大化」:🍓 +5)を牧場に取り込める場合があります。つまり「どの方向の戦線に柵予算 K を投資するか」というナップサック問題的なトレードオフが絡みます。
-5 点)」の存在:🕳️ (-5) があれば、余った柵を1本使ってその袋小路を切り離す(あるいは 🕳️ の真上に丸太の柵を打ち込んで穴を塞ぐ)ことでスコアが +5 点改善します。逆に、🕳️ (-5) のさらに奥に 🍓×3 (+15) が眠っていれば、あえて穴ぼこを塞がず奥へ道をつなぐ方が得になります(Stage 7『穴ぼこの向こう側』)。
それでは、このパズルを混合整数線形計画(MILP)として定式化していきましょう。通行可能な各マス v ∈ V に対して、以下の2種類の二値変数を導入します。
まず、自明な境界条件と柵の本数上限(予算制約)を書きます。
次に、「ヒツジの牧場 r_u = 1 の隣マス v に柵がなければ(f_v = 0)、その隣マス v も自動的に牧場と地続きになる(r_v = 1)」という物理法則を1本の線形不等式で表します。
このシンプルな不等式 r_u <= r_v + f_v は、1本で2つの重要な役割を同時に果たします。
r_s = 1)から始まる道の途中で一度も柵(f_v = 1)が置かれないままオオカミ(r_w = 0)に到達すると、1 = r_s <= … <= r_w = 0 となって制約違反(Infeasible)になります。つまり、すべての侵入経路のどこかに必ず f_v = 1 を置かなければ実行可能解になりません。
🕳️ -5 のつまみ食い防止):🍓 +5)だけ r_v = 1 にし、牧場の真ん中にある穴ぼこ(🕳️ -5)は都合よく r_v = 0 にする」という抜け道を使えてしまいます。r_u = 1)に隣接する穴ぼこ v は、丸太の柵を打ち込んで塞ぐ(f_v = 1, r_v = 0)か、牧場に含めて -5 点を受け入れる(f_v = 0, r_v = 1)かの二択を厳密に迫られます。
ここで数理モデリング上の興味深い落とし穴が登場します。実は、前節のフラッド制約だけでは、まだ正しい定式化にはなりません。
右上の 🍓 は木の柵と水辺💧でヒツジから完全に切り離されているのに、r_v = 1 にしてもフラッド制約 r_u <= r_v + f_v に違反しない。
ヒツジ🐑を水源とする水流(ネットワークフロー)が届くマスだけが r_v = 1 になれる。
なぜこの現象が起きるかというと、フラッド制約 r_u <= r_v + f_v は「ヒツジから到達できるマスはすべて 1 になる(下界)」は強制しますが、「柵や壁で囲まれた別の孤立ポケットの中身をまるごと r_v = 1 にする」ことは禁止していないからです。
「r_v = 1 ならば、そのマス v はヒツジ S と実際にパスで繋がっていなければならない」という連結性を線形制約で表す定番の手法が、単一品種フロー保存則です。
隣接する各マスの組に有向連続変数 flow_{u→v} >= 0(マス u から隣マス v へ流す「水」の量)を用意し、以下の制約を加えます(M = |V| は通行可能マスの総数):
v を r_v = 1 にするためには、式(7)より「どこかから 1 単位の水が流れ込んでくる」必要があります。しかし式(5)・(6)より、水は r = 1 のマス同士の間しか流れず、かつ水を新たに生み出せる(流入超過が許される)のは式(7)の対象外であるヒツジのマス S だけです。S と柵なしで繋がっていない孤立ポケットには一滴も水が届かず、必ず r_v = 0 になります。
最後に目的関数です。「牧場の合計スコアを最大化する(第1優先)」とともに、同じスコアなら「使う柵の本数をできるだけ少なくする(第2優先)」という辞書式順序(Lexicographic Objective)を、重み係数 100 を使って1本の線形目的関数にまとめています。
また、ゲーム内の「💡 1手ヒント」ボタンは、あらかじめ用意した固定の答えを見ているのではなく、プレイヤーが現在盤面に置いている柵集合 F_cur を f_v == 1 (∀v ∈ F_cur) として固定した上でリアルタイムに再求解しています:
Z* に届く場合:f_v* = 1 (v ∉ F_cur) のうち1マスを盤面上で黄色く点滅させます。
Z* 未満にしかならない場合:// 1. フラッド制約(u が牧場内で隣 v に柵がなければ、v も必ず牧場内)
if (vFence) {
model.constraint(expr`${uReach} - ${vReach} - ${vFence} <= 0`);
} else {
model.constraint(expr`${uReach} - ${vReach} <= 0`);
}
// 2. 単一品種フロー(u と v の両方が牧場内のときのみフローを流せる)
const flowUV = model.continuous(`flow_${r}_${c}_to_${nr}_${nc}`, { lb: 0, ub: maxFlowM });
model.constraint(expr`${flowUV} - ${maxFlowM} * ${uReach} <= 0`);
model.constraint(expr`${flowUV} - ${maxFlowM} * ${vReach} <= 0`);
// 3. 非ヒツジ頂点でのフロー保存則(流入 - 流出 == r_v)
model.constraint(expr`${sum(inList)} - ${sum(outList)} - ${rVar} == 0`);
Target Audience: Readers familiar with the basics of Mixed-Integer Linear Programming (binary variables, linear inequalities, and objective functions).
This article explains how a cozy puzzle about enclosing sheep with wooden fences builds upon the classic Minimum s-t Vertex Cut problem while formulating negative-weight tiles (pitfalls) and strict region connectivity using linear constraints.
The board is modeled as an undirected grid graph G = (V, E), where orthogonally adjacent passable cells form the edge set E. Each vertex (cell) v ∈ V has the following properties:
| Board Tile | Set / Symbol | Fence Allowed | Weight (Score) w_v |
Role in the Mathematical Model |
|---|---|---|---|---|
| 🐑 Sheep | S ⊂ V |
No | +2 |
Pasture origin (Source; fixed to reach_s = 1) |
| 🐺 Wolf | W ⊂ V |
No | 0 |
Hazard source (fixed to reach_w = 0) |
| 🌱 Grass / 🍓 Strawberry | V_fence ⊂ V |
Yes | +1 / +5 |
Positive-reward vertices (impassable when a fence is placed) |
| 🕳️ Pitfall | V_fence ⊂ V |
Yes | -5 |
Negative-weight vertex (penalizes score if enclosed; can be plugged by driving a fence post into it) |
| 🌉 Stone Bridge | V_bridge ⊂ V |
No | +1 |
Passable but uncuttable vertex (infinite cut capacity) |
| 💧 Water / 🪨 Rock | V_wall |
No | — | Impassable natural walls (excluded from V) |
The player's goal is to place at most K fences so that all paths between the wolves W and the sheep S are blocked, while maximizing the total score of the connected pasture reachable from the sheep S.
Suppose we temporarily ignore tile scores and ask only: "What is the minimum number of fences required to separate the wolves W from the sheep S?"
Whereas the standard minimum cut problem removes graph edges, here we block grid cells (vertices) with fences. This corresponds to the Minimum s-t Vertex Cut problem in graph theory.
W and the sheep S equals the minimum number of fences needed to separate them.🌉 (uncuttable vertices) split a wolf's approach into three independent corridors, caging the wolf with a single fence right in front of its spawn is mathematically impossible.
Two structural features make this puzzle richer than a textbook minimum cut problem:
K:🍓 +5) beyond it. Deciding where to invest the fence budget K introduces a knapsack-like trade-off across multiple fronts.
🕳️ -5):🕳️ (-5) lies connected to the pasture, spending a fence to seal off that corridor—or driving a fence post directly into the pit to plug it—improves the score by +5. Conversely, if three strawberries (+15) lie behind a pitfall (-5), deliberately leaving the pitfall open inside the pasture to keep the path open yields a net gain of +10 (as seen in Stage 7).
To formulate the puzzle as a Mixed-Integer Linear Program (MILP), we introduce two sets of binary decision variables over the passable cells v ∈ V:
First, we state the boundary conditions, mutual exclusion, and the fence budget constraint:
Next, we express the propagation rule: "If cell u is inside the pasture (r_u = 1) and its neighbor v has no fence (f_v = 0), then v must also be inside the pasture (r_v = 1)."
This single linear inequality r_u <= r_v + f_v simultaneously enforces two essential properties:
r_s = 1) reaches a wolf (r_w = 0) without encountering a fence (f_v = 1), chaining the inequality along the path yields 1 = r_s <= … <= r_w = 0, which is infeasible. Thus every wolf-to-sheep path must contain at least one fence.
r_v = 1 on positive tiles (🍓 +5) while arbitrarily setting r_v = 0 on an unfenced pitfall (🕳️ -5) right next to the sheep. Because of the flood constraint, any pitfall adjacent to the pasture forces a strict choice: spend a fence post to plug/block it (f_v = 1, r_v = 0) or include it in the pasture and accept the -5 penalty (f_v = 0, r_v = 1).
Here we encounter a classic mathematical modeling pitfall: the flood constraint alone is not sufficient to define the pasture accurately.
The top-right 🍓 is completely walled off from the sheep by a fence and water 💧, yet setting r_v = 1 there does not violate r_u <= r_v + f_v.
Only cells that can receive network flow originating from the sheep 🐑 can set r_v = 1.
Why does this happen? Because r_u <= r_v + f_v forces every cell reachable from the sheep to have r_v = 1 (a lower bound), but it does not forbid setting r_v = 1 inside an isolated pocket walled off by fences and water.
To guarantee that r_v = 1 holds only if cell v is genuinely connected to at least one sheep in S, we add Single-Commodity Flow conservation constraints.
For each directed edge (u, v) ∈ E, we define a non-negative continuous variable flow_{u→v} >= 0 representing the amount of flow sent from u to v (where M = |V| is the total number of passable cells):
r_v = 1 must consume 1 net unit of flow. By (5) and (6), flow can only travel through cells where r = 1, and the only nodes exempt from flow conservation (able to act as flow sources) are the sheep cells S. Consequently, any pocket disconnected from the sheep receives zero flow and is forced to r_v = 0.
Finally, we combine "maximizing total pasture score (primary objective)" and "using as few fences as possible among tied scores (secondary tie-breaker)" into a single linear objective using a lexicographic weight of 100:
The in-game "💡 1-Move Hint" button does not merely look up a static answer key. Instead, it fixes the player's currently placed fences F_cur (f_v == 1 for all v ∈ F_cur) and re-solves the MILP in real time:
Z*:f_v* = 1 (v ∉ F_cur) in yellow.
Z*:// 1. Flood constraint (if u is in the pasture and v has no fence, v must also be in the pasture)
if (vFence) {
model.constraint(expr`${uReach} - ${vReach} - ${vFence} <= 0`);
} else {
model.constraint(expr`${uReach} - ${vReach} <= 0`);
}
// 2. Single-commodity flow (flow is active only when both u and v are in the pasture)
const flowUV = model.continuous(`flow_${r}_${c}_to_${nr}_${nc}`, { lb: 0, ub: maxFlowM });
model.constraint(expr`${flowUV} - ${maxFlowM} * ${uReach} <= 0`);
model.constraint(expr`${flowUV} - ${maxFlowM} * ${vReach} <= 0`);
// 3. Flow conservation at every non-sheep cell (inflow - outflow == r_v)
model.constraint(expr`${sum(inList)} - ${sum(outList)} - ${rVar} == 0`);
适合读者:了解混合整数线性规划(MIP)基础(0-1变量、线性不等式与目标函数)的读者。
本文将解说这款看似轻松治愈的“用木栅栏围住小羊”的益智游戏,是如何以图论中的最小点割问题为基础,将“坑洞(负权重)”与“排除孤立飞地(连通性约束)”转化为严格的线性约束的。
游戏棋盘可以表示为无向网格图 G = (V, E)。上下左右相邻的可通行格构成边集 E,每个顶点(格子)v ∈ V 具有以下属性:
| 棋盘地形 | 集合与符号 | 可否放置栅栏 | 权重(得分) w_v |
数学模型中的角色 |
|---|---|---|---|---|
| 🐑 绵羊 | S ⊂ V |
不可 | +2 |
牧场起点(源点 Source,固定为 reach_s = 1) |
| 🐺 野狼 | W ⊂ V |
不可 | 0 |
入侵源(强制要求 reach_w = 0) |
| 🌱 草地 / 🍓 野草莓 | V_fence ⊂ V |
可以 | +1 / +5 |
正收益普通顶点(放置栅栏后不可通行) |
| 🕳️ 坑洞 | V_fence ⊂ V |
可以 | -5 |
负权重顶点(纳入牧场会扣分,可直接打入木桩栅栏将其填平) |
| 🌉 石桥 | V_bridge ⊂ V |
不可 | +1 |
可通行但不可割的顶点(割容量 ∞) |
| 💧 水域 / 🪨 岩石 | V_wall |
不可 | — | 天然障碍物(直接从图 V 中排除) |
玩家的目标是放置最多 K 根栅栏,阻断所有野狼 W 通往绵羊 S 的路径,并在该条件下最大化从绵羊 S 出发可步行的连通区域(安全牧场)的总得分。
我们先暂时忽略草莓与坑洞的分数,只考虑子问题:“最少需要几根栅栏才能将野狼 W 与绵羊 S 完全隔开?”
标准的最小割问题切断的是图中的“边(Edge)”,而本作是在“格子(顶点 Vertex)”上放置栅栏阻断通行,因此在图论中对应最小 s-t 点割问题(Minimum Vertex Cut)。
W 到绵羊 S 之间顶点不相交(不共用任何可放栅栏格)的最大路径条数,严格等于将两者隔开所需的最少栅栏数”。🌉(不可放置栅栏的顶点)分叉出多条进攻路线时,玩家就无法用1根栅栏直接在野狼面前将其堵死。
本作比单纯的最小割问题更有深度,主要源于以下两个机制:
K 下最大化区域价值”:🍓 +5)纳入牧场。也就是说,玩家需要在不同防线之间权衡如何分配有限的栅栏预算 K。
-5 分)”的取舍:🕳️ (-5),用1根富余的栅栏把死胡同隔开(或者直接在 🕳️ 上打入木桩栅栏将其填平)就能让总分提高 +5 分。反过来,如果 🕳️ (-5) 的更深处藏着 🍓×3 (+15),那么故意保留坑洞不封死、跨过它去拿深处的草莓反而能净赚 +10 分(如第7关)。
下面我们将该问题建立为混合整数线性规划(MILP)。对每个可通行格 v ∈ V,引入以下两类 0-1 决策变量:
首先写出边界条件、互斥条件与栅栏预算上限:
接着,用一条线性不等式刻画连通传播规律:“如果格子 u 在牧场内(r_u = 1),且相邻格子 v 没有放栅栏(f_v = 0),那么相邻格子 v 也必须自动属于牧场(r_v = 1)”。
这条简洁的不等式 r_u <= r_v + f_v 同时承担了两个重要作用:
r_s = 1)出发的某条路径在到达野狼(r_w = 0)之前没有任何一格放置栅栏(f_v = 1),沿路径串联不等式就会得到 1 = r_s <= … <= r_w = 0,从而导致模型无解(Infeasible)。因此所有入侵路径上都必须至少放置1根栅栏。
🕳️ -5 只捡正分格):🍓 +5)设为 r_v = 1,而把牧场中间没放栅栏的坑洞(🕳️ -5)偷偷设为 r_v = 0。r_u = 1)相邻的坑洞 v 都必须二选一:要么花1根木桩栅栏填平它(f_v = 1, r_v = 0),要么将它纳入牧场并接受 -5 分(f_v = 0, r_v = 1)。
这里会出现一个经典的数学建模陷阱:仅靠上一节的漫延约束,还不足以正确定义连通牧场。
右上角的 🍓 已被栅栏和水域💧完全隔绝在绵羊不可达之处,但将其设为 r_v = 1 并不违反 r_u <= r_v + f_v。
只有从绵羊🐑出发的“水流(网络流)”能够送达的格子,才能设为 r_v = 1。
原因在于:漫延约束 r_u <= r_v + f_v 只规定了“绵羊能走到的格子必须为 1(下界)”,却没有禁止求解器把被栅栏和墙壁围起来的另一块孤立口袋整体设为 r_v = 1 来白拿里面的草莓分数。
为了用线性约束严格表达“若 r_v = 1,则格子 v 必须与某只绵羊 S 存在真实连通路径”,标准做法是引入单商品流守恒约束。
对每对相邻有向边 (u, v) ∈ E 引入非负连续变量 flow_{u→v} >= 0(表示从格子 u 流向相邻格子 v 的“水量”,其中 M = |V| 为可通行格总数):
r_v = 1,就必须有 1 单位的净水流注入该格。而由式(5)・(6)可知,水流只能在 r = 1 的格子之间传递,且全图中唯一不受式(7)约束、能够凭空产生水流的源点只有绵羊所在格 S。r_v 必然被强制为 0。
最后是目标函数。我们将“最大化牧场总得分(第一优先级)”与“同分时尽量少用栅栏(第二优先级)”通过字典序权重系数 100 合并为一条线性目标函数:
此外,游戏中的“💡 一步提示”按钮并不是简单读取预设答案,而是将玩家当前已在棋盘上放置的栅栏集合 F_cur 作为固定约束 f_v == 1 (∀v ∈ F_cur) 加入模型并实时重求解:
Z*:f_v* = 1 (v ∉ F_cur) 中挑出1格在棋盘上以黄色高亮提示。
Z*:// 1. 漫延约束(若 u 在牧场内且相邻 v 未放栅栏,则 v 也必定在牧场内)
if (vFence) {
model.constraint(expr`${uReach} - ${vReach} - ${vFence} <= 0`);
} else {
model.constraint(expr`${uReach} - ${vReach} <= 0`);
}
// 2. 单商品流(仅当 u 与 v 均在牧场内时才允许流量通过)
const flowUV = model.continuous(`flow_${r}_${c}_to_${nr}_${nc}`, { lb: 0, ub: maxFlowM });
model.constraint(expr`${flowUV} - ${maxFlowM} * ${uReach} <= 0`);
model.constraint(expr`${flowUV} - ${maxFlowM} * ${vReach} <= 0`);
// 3. 非绵羊顶点的流量守恒(流入 - 流出 == r_v)
model.constraint(expr`${sum(inList)} - ${sum(outList)} - ${rVar} == 0`);