Files
Yudong Jin 22b3b568ef fix Ru translation (#1894)
* docs(ru): replace prose quotes with guillemets

* docs(ru): replace prose semicolons with periods

* docs(ru): align animation title forms

* docs(ru): align figure and table references
2026-04-14 18:10:12 +08:00

100 lines
9.0 KiB
Markdown

# Задача о максимальной вместимости
!!! question
Дан массив $ht$, где каждый элемент обозначает высоту вертикальной перегородки. Любые две перегородки в массиве вместе с пространством между ними образуют контейнер.
Вместимость контейнера равна произведению высоты и ширины (площади), где высота определяется более короткой перегородкой, а ширина - разностью индексов двух перегородок в массиве.
Требуется выбрать две перегородки так, чтобы образованный ими контейнер имел максимальную вместимость. Пример показан на рисунке ниже.
![Пример данных для задачи о максимальной вместимости](max_capacity_problem.assets/max_capacity_example.png)
Контейнер образуется произвольными двумя перегородками, **поэтому состоянием задачи служит пара индексов этих перегородок, обозначим ее как $[i, j]$**.
Согласно условию, вместимость равна произведению высоты на ширину, где высота определяется короткой перегородкой, а ширина - разностью индексов двух перегородок. Обозначим вместимость через $cap[i, j]$, тогда формула принимает вид:
$$
cap[i, j] = \min(ht[i], ht[j]) \times (j - i)
$$
Пусть длина массива равна $n$. Тогда число пар перегородок, то есть общее число состояний, равно $C_n^2 = \frac{n(n - 1)}{2}$. Самый прямолинейный подход - **перебрать все состояния**, после чего найти максимальную вместимость. Его временная сложность равна $O(n^2)$.
### Определение жадной стратегии
У этой задачи есть и более эффективное решение. Как показано на рисунке ниже, рассмотрим состояние $[i, j]$, где индексы удовлетворяют $i < j$, а высоты - условию $ht[i] < ht[j]$, то есть $i$ - короткая перегородка, а $j$ - длинная.
![Начальное состояние](max_capacity_problem.assets/max_capacity_initial_state.png)
Как показано на рисунке ниже, **если в этот момент сдвинуть длинную перегородку $j$ ближе к короткой перегородке $i$, то вместимость обязательно уменьшится**.
Причина в том, что после смещения длинной перегородки $j$ ширина $j-i$ обязательно станет меньше, а высота определяется короткой перегородкой, поэтому высота либо останется прежней (если $i$ останется короткой перегородкой), либо уменьшится (если сдвинутая $j$ станет короткой перегородкой).
![Состояние после перемещения длинной перегородки внутрь](max_capacity_problem.assets/max_capacity_moving_long_board.png)
Рассуждая в обратную сторону, **только сдвигая короткую перегородку $i$ внутрь, мы можем получить шанс увеличить вместимость**. Хотя ширина при этом обязательно уменьшится, **высота может возрасти** (если после перемещения короткая перегородка $i$ станет выше). Например, на рисунке ниже после перемещения короткой перегородки площадь увеличивается.
![Состояние после перемещения короткой перегородки внутрь](max_capacity_problem.assets/max_capacity_moving_short_board.png)
Отсюда и выводится жадная стратегия для этой задачи: инициализировать два указателя по краям контейнера и на каждом шаге сдвигать внутрь указатель, соответствующий короткой перегородке, пока указатели не встретятся.
На рисунке ниже показан процесс выполнения этой жадной стратегии.
1. В начальном состоянии указатели $i$ и $j$ стоят на двух концах массива.
2. Вычислить вместимость текущего состояния $cap[i, j]$ и обновить максимальную вместимость.
3. Сравнить высоты перегородок $i$ и $j$, после чего сдвинуть короткую перегородку на одну позицию внутрь.
4. Повторять шаги `2.` и `3.` до тех пор, пока $i$ и $j$ не встретятся.
=== "<1>"
![Жадный процесс решения задачи о максимальной вместимости](max_capacity_problem.assets/max_capacity_greedy_step1.png)
=== "<2>"
![max_capacity_greedy_step2](max_capacity_problem.assets/max_capacity_greedy_step2.png)
=== "<3>"
![max_capacity_greedy_step3](max_capacity_problem.assets/max_capacity_greedy_step3.png)
=== "<4>"
![max_capacity_greedy_step4](max_capacity_problem.assets/max_capacity_greedy_step4.png)
=== "<5>"
![max_capacity_greedy_step5](max_capacity_problem.assets/max_capacity_greedy_step5.png)
=== "<6>"
![max_capacity_greedy_step6](max_capacity_problem.assets/max_capacity_greedy_step6.png)
=== "<7>"
![max_capacity_greedy_step7](max_capacity_problem.assets/max_capacity_greedy_step7.png)
=== "<8>"
![max_capacity_greedy_step8](max_capacity_problem.assets/max_capacity_greedy_step8.png)
=== "<9>"
![max_capacity_greedy_step9](max_capacity_problem.assets/max_capacity_greedy_step9.png)
### Код реализации
Цикл в коде выполняется не более $n$ раз, **поэтому временная сложность равна $O(n)$**.
Переменные $i$, $j$, $res$ используют дополнительную память постоянного размера, **поэтому пространственная сложность равна $O(1)$**.
```src
[file]{max_capacity}-[class]{}-[func]{max_capacity}
```
### Доказательство корректности
Жадный алгоритм быстрее полного перебора именно потому, что каждый жадный шаг «пропускает» часть состояний.
Например, в состоянии $cap[i, j]$ перегородка $i$ является короткой, а $j$ - длинной. Если жадно сдвинуть короткую перегородку $i$ на одну позицию внутрь, то состояния, показанные на рисунке ниже, будут «пропущены». **Это означает, что позже мы уже не сможем проверить вместимость этих состояний**.
$$
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)
Нетрудно заметить, что **эти пропущенные состояния на самом деле и есть все состояния, в которых длинная перегородка $j$ сдвигается внутрь**. Ранее мы уже доказали, что перемещение длинной перегородки внутрь обязательно уменьшает вместимость. Иными словами, пропущенные состояния не могут быть оптимальным решением, **поэтому их пропуск не приводит к потере оптимума**.
Приведенный анализ показывает, что операция перемещения короткой перегородки является «безопасной», а жадная стратегия действительно корректна.