This commit is contained in:
krahets
2026-04-03 18:46:15 +08:00
parent 377736b1bd
commit 9d21ca86b0
352 changed files with 46563 additions and 11262 deletions
+261 -59
View File
@@ -6,59 +6,59 @@ comments: true
!!! question
配列 $ht$ を入力します。各要素は垂直仕切りの高さを表します。配列内の任意の2つの仕切りと、それらの間のスペースによってコンテナを形成できます。
配列 $ht$ が与えられ、各要素は垂直仕切りの高さを表します。配列内の任意の 2 枚の仕切りと、その間の空間で容器を構成できます。
容器の容量は高さと幅の積(面積)に等しく、高さは短い方の仕切り板で決まり、幅は 2 枚の仕切り板の配列インデックスの差です。
配列から 2 枚の仕切り板を選び、構成される容器の容量が最大となるようにしてください。最大容量を返します。例を以下の図に示します。
コンテナの容量は高さと幅の積(面積)で、高さは短い方の仕切りによって決定され、幅は2つの仕切りの配列インデックスの差です。
![最大容量問題のサンプルデータ](max_capacity_problem.assets/max_capacity_example.png){ class="animation-figure" }
コンテナの容量を最大化する2つの仕切りを配列から選択し、この最大容量を返してください。例を下の図に示します。
<p align="center"> 図 15-7 &nbsp; 最大容量問題のサンプルデータ </p>
![最大容量問題の例データ](max_capacity_problem.assets/max_capacity_example.png){ class="animation-figure" }
容器は任意の 2 枚の仕切り板で囲まれるため、**本問の状態は 2 枚の仕切り板のインデックスで表され、$[i, j]$ と記します**。
<p align="center"> 図 15-7 &nbsp; 最大容量問題の例データ </p>
コンテナは任意の2つの仕切りによって形成されるため、**この問題の状態は2つの仕切りのインデックスで表現され、$[i, j]$ と表記されます**。
問題の記述によれば、容量は高さと幅の積に等しく、高さは短い方の仕切りによって決定され、幅は2つの仕切りの配列インデックスの差です。容量 $cap[i, j]$ の式は:
問題の条件より、容量は高さと幅の積に等しく、高さは短い板で決まり、幅は 2 枚の仕切り板の配列インデックスの差です。容量を $cap[i, j]$ とすると、計算式は次のようになります。
$$
cap[i, j] = \min(ht[i], ht[j]) \times (j - i)
$$
配列の長さを $n$ と仮定すると、2の仕切りの組み合わせ数(状態総数)は $C_n^2 = \frac{n(n - 1)}{2}$ です。最も直接的なアプローチは**すべての可能な状態を列挙する**ことで、時間計算量は $O(n^2)$ になります。
配列の長さを $n$ とすると、2の仕切りの組せ数(状態総数)は $C_n^2 = \frac{n(n - 1)}{2}$ です。最も直接的には、**すべての状態を総当たりできます**。これにより最大容量を求められ、時間計算量は $O(n^2)$ す。
### 1. &nbsp; 貪欲戦略の決定
この問題にはより効率的な解法があります。下の図に示すように、インデックス $i < j$ かつ高さ $ht[i] < ht[j]$ の状態 $[i, j]$ を選択します。つまり、$i$ 短い仕切り、$j$ は高い仕切りです。
この問題にはさらに効率的な解法があります。下の図ように、状態 $[i, j]$ を 1 つ選び、インデックス $i < j$ かつ高さ $ht[i] < ht[j]$ を満たすとします。つまり、$i$ 短い、$j$ が長い板です。
![初期状態](max_capacity_problem.assets/max_capacity_initial_state.png){ class="animation-figure" }
<p align="center"> 図 15-8 &nbsp; 初期状態 </p>
下の図に示すように、**高い仕切り $j$ を短い仕切り $i$ に近づけて移動すると、容量は確実に減少します**。
下の図ように、**このとき長い板 $j$ を短い $i$ に近づけると、容量は必ず小さくなります**。
これは、高い仕切り $j$ を移動すると、幅 $j-i$ が確実に減少するためです。高さは短い仕切りによって決定されるため、高さは同じまま($i$ が短い仕切りのまま)か減少(移動した $j$ が短い仕切りになる)しかありません。
これは、長い板 $j$ を動かした後は幅 $j-i$ が必ず小さくなるためです。また、高さは短い板で決まるので、高さは変わらない( $i$ が依然として短い板)か、小さくなる(移動後の $j$ が短いになる)ことしかありません。
![高い仕切りを内側に移動した後の状態](max_capacity_problem.assets/max_capacity_moving_long_board.png){ class="animation-figure" }
![長い板を内側へ動かした後の状態](max_capacity_problem.assets/max_capacity_moving_long_board.png){ class="animation-figure" }
<p align="center"> 図 15-9 &nbsp; 高い仕切りを内側に移動した後の状態 </p>
<p align="center"> 図 15-9 &nbsp; 長い板を内側へ動かした後の状態 </p>
逆に、**短い仕切り $i$ を内側に移動することによってのみ容量を増加させることが可能です**。幅は確実に減少しますが、**高さが増加する可能性があります**(移動した短い仕切り $i$ が高くなる場合)。例えば、下の図では、短い仕切りを移動した後に面積が増加しています。
逆に考えると、**短い $i$ を内側へ縮めた場合にのみ容量が大きくなる可能性があります**。幅は必ず小さくなりますが、**高さは大きくなる可能性がある**からです(移動後の短い板 $i$ がより長くなる可能性があります)。たとえば次の図では、短い板を動かした後に面積が大きくなっています。
![短い仕切りを内側に移動した後の状態](max_capacity_problem.assets/max_capacity_moving_short_board.png){ class="animation-figure" }
![短い板を内側へ動かした後の状態](max_capacity_problem.assets/max_capacity_moving_short_board.png){ class="animation-figure" }
<p align="center"> 図 15-10 &nbsp; 短い仕切りを内側に移動した後の状態 </p>
<p align="center"> 図 15-10 &nbsp; 短い板を内側へ動かした後の状態 </p>
これにより、この問題の貪欲戦略が導かれます:コンテナの両端に2つのポインタを初期化し、各ラウンドで短い仕切りに対応するポインタを内側に移動し、2つのポインタが出会うまで続けます。
以上から、本問の貪欲戦略を導けます。2 本のポインタを初期化して容器の両端に置き、各ラウンドで短いに対応するポインタを内側へ縮め、2 本のポインタが出会うまで続けます。
下の図は貪欲戦略の実行を示しています。
下の図は貪欲戦略の実行過程を示しています。
1. 最初に、ポインタ $i$ と $j$ 配列の両端に配置されます。
1. 初期状態では、ポインタ $i$ と $j$ 配列の両端にあります。
2. 現在の状態の容量 $cap[i, j]$ を計算し、最大容量を更新します。
3. 仕切り $i$ と $j$ の高さを比較し、短い仕切りを1ステップ内側に移動します。
4. $i$ と $j$ が出会うまでステップ `2.``3.` を繰り返します。
3. $i$ と $j$ の高さを比較し、短い板を内側へ 1 マス移動します。
4. `2.``3.` を繰り返し実行し、$i$ と $j$ が出会ったら終了します。
=== "<1>"
![最大容量問題の貪欲プロセス](max_capacity_problem.assets/max_capacity_greedy_step1.png){ class="animation-figure" }
![最大容量問題の貪欲な過程](max_capacity_problem.assets/max_capacity_greedy_step1.png){ class="animation-figure" }
=== "<2>"
![max_capacity_greedy_step2](max_capacity_problem.assets/max_capacity_greedy_step2.png){ class="animation-figure" }
@@ -84,29 +84,29 @@ $$
=== "<9>"
![max_capacity_greedy_step9](max_capacity_problem.assets/max_capacity_greedy_step9.png){ class="animation-figure" }
<p align="center"> 図 15-11 &nbsp; 最大容量問題の貪欲プロセス </p>
<p align="center"> 図 15-11 &nbsp; 最大容量問題の貪欲な過程 </p>
### 2. &nbsp; 実装
### 2. &nbsp; コード実装
コードは最大 $n$ 回ループするため、**時間計算量は $O(n)$** です。
コードのループ回数は最大でも $n$ 回であるため、**時間計算量は $O(n)$** です。
変数 $i$、$j$、$res$ は一定量の追加スペースを使用するため、**空間計算量は $O(1)$** です。
変数 $i$、$j$、$res$ が使う追加領域は定数サイズなので、**空間計算量は $O(1)$** です。
=== "Python"
```python title="max_capacity.py"
def max_capacity(ht: list[int]) -> int:
"""最大容量:貪欲法"""
# ij を初期化、配列の両端で分割させる
# i, j を初期化し、それぞれ配列の両端に置く
i, j = 0, len(ht) - 1
# 初期最大容量は 0
# 初期最大容量は 0
res = 0
# 2 の板が出会うまで貪欲選択をループ
# 2 の板が出会うまで貪欲選択を繰り返す
while i < j:
# 最大容量を更新
# 最大容量を更新する
cap = min(ht[i], ht[j]) * (j - i)
res = max(res, cap)
# 短いを内側に移動
# 短いを内側へ動かす
if ht[i] < ht[j]:
i += 1
else:
@@ -119,16 +119,16 @@ $$
```cpp title="max_capacity.cpp"
/* 最大容量:貪欲法 */
int maxCapacity(vector<int> &ht) {
// ij を初期化し、配列の両端で分割させる
// i, j を初期化し、それぞれ配列の両端に置く
int i = 0, j = ht.size() - 1;
// 初期最大容量は 0
// 初期最大容量は 0
int res = 0;
// 2の板が出会うまで貪欲選択をループ
// 2の板が出会うまで貪欲選択を繰り返す
while (i < j) {
// 最大容量を更新
// 最大容量を更新する
int cap = min(ht[i], ht[j]) * (j - i);
res = max(res, cap);
// より短いを内側に移動
// 短いを内側へ動かす
if (ht[i] < ht[j]) {
i++;
} else {
@@ -144,16 +144,16 @@ $$
```java title="max_capacity.java"
/* 最大容量:貪欲法 */
int maxCapacity(int[] ht) {
// ij を初期化し、配列の両端で分割させる
// i, j を初期化し、それぞれ配列の両端に置く
int i = 0, j = ht.length - 1;
// 初期最大容量は 0
// 初期最大容量は 0
int res = 0;
// 2の板が出会うまで貪欲選択をループ
// 2の板が出会うまで貪欲選択を繰り返す
while (i < j) {
// 最大容量を更新
// 最大容量を更新する
int cap = Math.min(ht[i], ht[j]) * (j - i);
res = Math.max(res, cap);
// より短いを内側に移動
// 短いを内側へ動かす
if (ht[i] < ht[j]) {
i++;
} else {
@@ -167,77 +167,279 @@ $$
=== "C#"
```csharp title="max_capacity.cs"
[class]{max_capacity}-[func]{MaxCapacity}
/* 最大容量:貪欲法 */
int MaxCapacity(int[] ht) {
// i, j を初期化し、それぞれ配列の両端に置く
int i = 0, j = ht.Length - 1;
// 初期の最大容量は 0
int res = 0;
// 2 枚の板が出会うまで貪欲選択を繰り返す
while (i < j) {
// 最大容量を更新する
int cap = Math.Min(ht[i], ht[j]) * (j - i);
res = Math.Max(res, cap);
// 短い方を内側へ動かす
if (ht[i] < ht[j]) {
i++;
} else {
j--;
}
}
return res;
}
```
=== "Go"
```go title="max_capacity.go"
[class]{}-[func]{maxCapacity}
/* 最大容量:貪欲法 */
func maxCapacity(ht []int) int {
// i, j を初期化し、それぞれ配列の両端に置く
i, j := 0, len(ht)-1
// 初期の最大容量は 0
res := 0
// 2 枚の板が出会うまで貪欲選択を繰り返す
for i < j {
// 最大容量を更新する
capacity := int(math.Min(float64(ht[i]), float64(ht[j]))) * (j - i)
res = int(math.Max(float64(res), float64(capacity)))
// 短い方を内側へ動かす
if ht[i] < ht[j] {
i++
} else {
j--
}
}
return res
}
```
=== "Swift"
```swift title="max_capacity.swift"
[class]{}-[func]{maxCapacity}
/* 最大容量:貪欲法 */
func maxCapacity(ht: [Int]) -> Int {
// i, j を初期化し、それぞれ配列の両端に置く
var i = ht.startIndex, j = ht.endIndex - 1
// 初期の最大容量は 0
var res = 0
// 2 枚の板が出会うまで貪欲選択を繰り返す
while i < j {
// 最大容量を更新する
let cap = min(ht[i], ht[j]) * (j - i)
res = max(res, cap)
// 短い方を内側へ動かす
if ht[i] < ht[j] {
i += 1
} else {
j -= 1
}
}
return res
}
```
=== "JS"
```javascript title="max_capacity.js"
[class]{}-[func]{maxCapacity}
/* 最大容量:貪欲法 */
function maxCapacity(ht) {
// i, j を初期化し、それぞれ配列の両端に置く
let i = 0,
j = ht.length - 1;
// 初期の最大容量は 0
let res = 0;
// 2 枚の板が出会うまで貪欲選択を繰り返す
while (i < j) {
// 最大容量を更新する
const cap = Math.min(ht[i], ht[j]) * (j - i);
res = Math.max(res, cap);
// 短い方を内側へ動かす
if (ht[i] < ht[j]) {
i += 1;
} else {
j -= 1;
}
}
return res;
}
```
=== "TS"
```typescript title="max_capacity.ts"
[class]{}-[func]{maxCapacity}
/* 最大容量:貪欲法 */
function maxCapacity(ht: number[]): number {
// i, j を初期化し、それぞれ配列の両端に置く
let i = 0,
j = ht.length - 1;
// 初期の最大容量は 0
let res = 0;
// 2 枚の板が出会うまで貪欲選択を繰り返す
while (i < j) {
// 最大容量を更新する
const cap: number = Math.min(ht[i], ht[j]) * (j - i);
res = Math.max(res, cap);
// 短い方を内側へ動かす
if (ht[i] < ht[j]) {
i += 1;
} else {
j -= 1;
}
}
return res;
}
```
=== "Dart"
```dart title="max_capacity.dart"
[class]{}-[func]{maxCapacity}
/* 最大容量:貪欲法 */
int maxCapacity(List<int> ht) {
// i, j を初期化し、それぞれ配列の両端に置く
int i = 0, j = ht.length - 1;
// 初期の最大容量は 0
int res = 0;
// 2 枚の板が出会うまで貪欲選択を繰り返す
while (i < j) {
// 最大容量を更新する
int cap = min(ht[i], ht[j]) * (j - i);
res = max(res, cap);
// 短い方を内側へ動かす
if (ht[i] < ht[j]) {
i++;
} else {
j--;
}
}
return res;
}
```
=== "Rust"
```rust title="max_capacity.rs"
[class]{}-[func]{max_capacity}
/* 最大容量:貪欲法 */
fn max_capacity(ht: &[i32]) -> i32 {
// i, j を初期化し、それぞれ配列の両端に置く
let mut i = 0;
let mut j = ht.len() - 1;
// 初期の最大容量は 0
let mut res = 0;
// 2 枚の板が出会うまで貪欲選択を繰り返す
while i < j {
// 最大容量を更新する
let cap = std::cmp::min(ht[i], ht[j]) * (j - i) as i32;
res = std::cmp::max(res, cap);
// 短い方を内側へ動かす
if ht[i] < ht[j] {
i += 1;
} else {
j -= 1;
}
}
res
}
```
=== "C"
```c title="max_capacity.c"
[class]{}-[func]{maxCapacity}
/* 最大容量:貪欲法 */
int maxCapacity(int ht[], int htLength) {
// i, j を初期化し、それぞれ配列の両端に置く
int i = 0;
int j = htLength - 1;
// 初期の最大容量は 0
int res = 0;
// 2 枚の板が出会うまで貪欲選択を繰り返す
while (i < j) {
// 最大容量を更新する
int capacity = myMin(ht[i], ht[j]) * (j - i);
res = myMax(res, capacity);
// 短い方を内側へ動かす
if (ht[i] < ht[j]) {
i++;
} else {
j--;
}
}
return res;
}
```
=== "Kotlin"
```kotlin title="max_capacity.kt"
[class]{}-[func]{maxCapacity}
/* 最大容量:貪欲法 */
fun maxCapacity(ht: IntArray): Int {
// i, j を初期化し、それぞれ配列の両端に置く
var i = 0
var j = ht.size - 1
// 初期の最大容量は 0
var res = 0
// 2 枚の板が出会うまで貪欲選択を繰り返す
while (i < j) {
// 最大容量を更新する
val cap = min(ht[i], ht[j]) * (j - i)
res = max(res, cap)
// 短い方を内側へ動かす
if (ht[i] < ht[j]) {
i++
} else {
j--
}
}
return res
}
```
=== "Ruby"
```ruby title="max_capacity.rb"
[class]{}-[func]{max_capacity}
### 最大容量:貪欲法 ###
def max_capacity(ht)
# i, j を初期化し、それぞれ配列の両端に置く
i, j = 0, ht.length - 1
# 初期の最大容量は 0
res = 0
# 2 枚の板が出会うまで貪欲選択を繰り返す
while i < j
# 最大容量を更新する
cap = [ht[i], ht[j]].min * (j - i)
res = [res, cap].max
# 短い方を内側へ動かす
if ht[i] < ht[j]
i += 1
else
j -= 1
end
end
res
end
```
??? pythontutor "コードの可視化"
<div style="height: 549px; width: 100%;"><iframe class="pythontutor-iframe" src="https://pythontutor.com/iframe-embed.html#code=def%20max_capacity%28ht%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%9C%80%E5%A4%A7%E5%AE%B9%E9%87%8F%EF%BC%9A%E8%B2%AA%E6%AC%B2%E6%B3%95%22%22%22%0A%20%20%20%20%23%20i%2C%20j%20%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%E3%81%97%E3%80%81%E3%81%9D%E3%82%8C%E3%81%9E%E3%82%8C%E9%85%8D%E5%88%97%E3%81%AE%E4%B8%A1%E7%AB%AF%E3%81%AB%E7%BD%AE%E3%81%8F%0A%20%20%20%20i%2C%20j%20%3D%200%2C%20len%28ht%29%20-%201%0A%20%20%20%20%23%20%E5%88%9D%E6%9C%9F%E3%81%AE%E6%9C%80%E5%A4%A7%E5%AE%B9%E9%87%8F%E3%81%AF%200%0A%20%20%20%20res%20%3D%200%0A%20%20%20%20%23%202%20%E6%9E%9A%E3%81%AE%E6%9D%BF%E3%81%8C%E5%87%BA%E4%BC%9A%E3%81%86%E3%81%BE%E3%81%A7%E8%B2%AA%E6%AC%B2%E9%81%B8%E6%8A%9E%E3%82%92%E7%B9%B0%E3%82%8A%E8%BF%94%E3%81%99%0A%20%20%20%20while%20i%20%3C%20j%3A%0A%20%20%20%20%20%20%20%20%23%20%E6%9C%80%E5%A4%A7%E5%AE%B9%E9%87%8F%E3%82%92%E6%9B%B4%E6%96%B0%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20cap%20%3D%20min%28ht%5Bi%5D%2C%20ht%5Bj%5D%29%20%2A%20%28j%20-%20i%29%0A%20%20%20%20%20%20%20%20res%20%3D%20max%28res%2C%20cap%29%0A%20%20%20%20%20%20%20%20%23%20%E7%9F%AD%E3%81%84%E6%96%B9%E3%82%92%E5%86%85%E5%81%B4%E3%81%B8%E5%8B%95%E3%81%8B%E3%81%99%0A%20%20%20%20%20%20%20%20if%20ht%5Bi%5D%20%3C%20ht%5Bj%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20-%3D%201%0A%20%20%20%20return%20res%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20ht%20%3D%20%5B3%2C%208%2C%205%2C%202%2C%207%2C%207%2C%203%2C%204%5D%0A%0A%20%20%20%20%23%20%E8%B2%AA%E6%AC%B2%E6%B3%95%0A%20%20%20%20res%20%3D%20max_capacity%28ht%29%0A%20%20%20%20print%28f%22%E6%9C%80%E5%A4%A7%E5%AE%B9%E9%87%8F%E3%81%AF%20%7Bres%7D%22%29&codeDivHeight=472&codeDivWidth=350&cumulative=false&curInstr=4&heapPrimitives=nevernest&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false"> </iframe></div>
<div style="margin-top: 5px;"><a href="https://pythontutor.com/iframe-embed.html#code=def%20max_capacity%28ht%3A%20list%5Bint%5D%29%20-%3E%20int%3A%0A%20%20%20%20%22%22%22%E6%9C%80%E5%A4%A7%E5%AE%B9%E9%87%8F%EF%BC%9A%E8%B2%AA%E6%AC%B2%E6%B3%95%22%22%22%0A%20%20%20%20%23%20i%2C%20j%20%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%E3%81%97%E3%80%81%E3%81%9D%E3%82%8C%E3%81%9E%E3%82%8C%E9%85%8D%E5%88%97%E3%81%AE%E4%B8%A1%E7%AB%AF%E3%81%AB%E7%BD%AE%E3%81%8F%0A%20%20%20%20i%2C%20j%20%3D%200%2C%20len%28ht%29%20-%201%0A%20%20%20%20%23%20%E5%88%9D%E6%9C%9F%E3%81%AE%E6%9C%80%E5%A4%A7%E5%AE%B9%E9%87%8F%E3%81%AF%200%0A%20%20%20%20res%20%3D%200%0A%20%20%20%20%23%202%20%E6%9E%9A%E3%81%AE%E6%9D%BF%E3%81%8C%E5%87%BA%E4%BC%9A%E3%81%86%E3%81%BE%E3%81%A7%E8%B2%AA%E6%AC%B2%E9%81%B8%E6%8A%9E%E3%82%92%E7%B9%B0%E3%82%8A%E8%BF%94%E3%81%99%0A%20%20%20%20while%20i%20%3C%20j%3A%0A%20%20%20%20%20%20%20%20%23%20%E6%9C%80%E5%A4%A7%E5%AE%B9%E9%87%8F%E3%82%92%E6%9B%B4%E6%96%B0%E3%81%99%E3%82%8B%0A%20%20%20%20%20%20%20%20cap%20%3D%20min%28ht%5Bi%5D%2C%20ht%5Bj%5D%29%20%2A%20%28j%20-%20i%29%0A%20%20%20%20%20%20%20%20res%20%3D%20max%28res%2C%20cap%29%0A%20%20%20%20%20%20%20%20%23%20%E7%9F%AD%E3%81%84%E6%96%B9%E3%82%92%E5%86%85%E5%81%B4%E3%81%B8%E5%8B%95%E3%81%8B%E3%81%99%0A%20%20%20%20%20%20%20%20if%20ht%5Bi%5D%20%3C%20ht%5Bj%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20i%20%2B%3D%201%0A%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20j%20-%3D%201%0A%20%20%20%20return%20res%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20ht%20%3D%20%5B3%2C%208%2C%205%2C%202%2C%207%2C%207%2C%203%2C%204%5D%0A%0A%20%20%20%20%23%20%E8%B2%AA%E6%AC%B2%E6%B3%95%0A%20%20%20%20res%20%3D%20max_capacity%28ht%29%0A%20%20%20%20print%28f%22%E6%9C%80%E5%A4%A7%E5%AE%B9%E9%87%8F%E3%81%AF%20%7Bres%7D%22%29&codeDivHeight=800&codeDivWidth=600&cumulative=false&curInstr=4&heapPrimitives=nevernest&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false" target="_blank" rel="noopener noreferrer">全画面で見る ></a></div>
### 3. &nbsp; 正しさの証明
貪欲法が列挙よりも高速である理由は、各ラウンドの貪欲選択が一部の状態を「スキップ」するからです。
貪欲法が総当たりより速いのは、各ラウンドの貪欲選択がいくつかの状態を「スキップ」するためです。
例えば、$i$ が短い仕切りで $j$ が高い仕切りである状態 $cap[i, j]$ の下で、短い仕切り $i$ を貪欲に1ステップ内側に移動すると、の図に示す「スキップされた」状態につながります。**これは、れらの状態の容量を後で検証できないことを意味します**。
たとえば状態 $cap[i, j]$ において、$i$ が短い板、$j$ が長い板だとします。貪欲に短い板 $i$ を内側へ 1 マス動かすと、の図に示す状態が「スキップされます。**これは、その後それらの状態の容量を検証できないことを意味します**。
$$
cap[i, i+1], cap[i, i+2], \dots, cap[i, j-2], cap[i, j-1]
$$
![短い仕切りの移動によってスキップされる状態](max_capacity_problem.assets/max_capacity_skipped_states.png){ class="animation-figure" }
![短いの移動によってスキップされる状態](max_capacity_problem.assets/max_capacity_skipped_states.png){ class="animation-figure" }
<p align="center"> 図 15-12 &nbsp; 短い仕切りの移動によってスキップされる状態 </p>
<p align="center"> 図 15-12 &nbsp; 短いの移動によってスキップされる状態 </p>
観察すると、**これらのスキップされた状態は実際には高い仕切り $j$ 内側に移動したすべての状態**です。高い仕切りを内側に移動すると容量が確実に減少することをすでに証明しました。したがって、スキップされた状態は最適解である可能性がなく、**それらをスキップしても最適解を逃すことはありません**。
観察すると、**これらのスキップされた状態は実際には長い板 $j$ 内側へ動かしたすべての状態そのものです**。前述のとおり、長い板を内側へ動かすと容量は必ず小さくなります。つまり、スキップされた状態はいずれも最適解にはなりえず、**それらを飛ばしても最適解を逃すことはありません**。
分析により、短い仕切りを移動する操作は「安全」であり、貪欲戦略が効果的であることが示されます。
以上の分析から、短い板を動かす操作は「安全」であり、貪欲戦略は有効であると分かります。