# Задача о максимальной вместимости !!! question Дан массив $ht$, каждый элемент которого представляет высоту вертикальной перегородки. Любые две перегородки в массиве и пространство между ними могут образовать контейнер. Вместимость контейнера равна произведению высоты и ширины (площади), где высота определяется более короткой перегородкой, а ширина — разностью индексов двух перегородок в массиве. Необходимо выбрать в массиве две перегородки так, чтобы вместимость образованного контейнера была максимальной, и вернуть максимальную вместимость. Пример показан на рисунке ниже. ![Пример данных для задачи о максимальной вместимости](../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$ — длинная перегородка. ![Начальное состояние](../assets/max_capacity_initial_state.png) Как показано на рисунке ниже, **если сейчас переместить длинную перегородку $j$ ближе к короткой перегородке $i$, вместимость обязательно уменьшится**. Это происходит потому, что после перемещения длинной перегородки $j$ ширина $j-i$ определенно уменьшится; а высота определяется короткой перегородкой, поэтому высота может только остаться неизменной ($i$ остается короткой перегородкой) или уменьшиться (перемещенная $j$ становится короткой перегородкой). ![Состояние после перемещения длинной перегородки внутрь](../assets/max_capacity_moving_long_board.png) Рассуждая в обратном направлении, **только сжимая короткую перегородку $i$ внутрь, мы можем увеличить вместимость**. Потому что хотя ширина определенно уменьшится, **высота может увеличиться** (перемещенная короткая перегородка $i$ может стать длиннее). Например, на рисунке ниже после перемещения короткой перегородки площадь увеличивается. ![Состояние после перемещения короткой перегородки внутрь](../assets/max_capacity_moving_short_board.png) Таким образом, можно вывести жадную стратегию для данной задачи: инициализировать два указателя по краям контейнера, на каждом шаге сжимать внутрь указатель, соответствующий короткой перегородке, пока два указателя не встретятся. На рисунке ниже показан процесс выполнения жадной стратегии. 1. В начальном состоянии указатели $i$ и $j$ находятся на противоположных концах массива. 2. Вычислить вместимость текущего состояния $cap[i, j]$ и обновить максимальную вместимость. 3. Сравнить высоты перегородок $i$ и $j$ и переместить короткую перегородку внутрь на одну позицию. 4. Циклически выполнять шаги `2.` и `3.`, пока $i$ и $j$ не встретятся. === "<1>" ![Жадный процесс решения задачи о максимальной вместимости](../assets/max_capacity_greedy_step1.png) === "<2>" ![max_capacity_greedy_step2](../assets/max_capacity_greedy_step2.png) === "<3>" ![max_capacity_greedy_step3](../assets/max_capacity_greedy_step3.png) === "<4>" ![max_capacity_greedy_step4](../assets/max_capacity_greedy_step4.png) === "<5>" ![max_capacity_greedy_step5](../assets/max_capacity_greedy_step5.png) === "<6>" ![max_capacity_greedy_step6](../assets/max_capacity_greedy_step6.png) === "<7>" ![max_capacity_greedy_step7](../assets/max_capacity_greedy_step7.png) === "<8>" ![max_capacity_greedy_step8](../assets/max_capacity_greedy_step8.png) === "<9>" ![max_capacity_greedy_step9](../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] $$ ![Пропущенные состояния при перемещении короткой перегородки](../assets/max_capacity_skipped_states.png) При наблюдении обнаруживается, что **эти пропущенные состояния фактически являются всеми состояниями при перемещении длинной перегородки $j$ внутрь**. Ранее мы уже доказали, что перемещение длинной перегородки внутрь обязательно приводит к уменьшению вместимости. То есть пропущенные состояния не могут быть оптимальным решением, **их пропуск не приведет к потере оптимального решения**. Приведенный выше анализ показывает, что операция перемещения короткой перегородки является «безопасной», жадная стратегия эффективна.