E147. Поиск подотрезка arregloа с максимальной/минимальной суммой

e-maxx algorithm original: C/C++ #algorithm #array #emaxx #misc #search
El texto de la tarea se traduce del ruso para el idioma seleccionado. El código no cambia.

Источник: e-maxx.ru/algo, страница PDF 484.

Здесь мы рассмотрим задачу о поиске подотрезка arregloа с максимальной суммой ("maximum subarray problem" на английском), а также некоторые её вариации (в том числе Algoritmo решения варианта этой задачи в режиме онлайн — описанный автором Algoritmoа — KADR (Ярослав Твердохлеб)).

Постановка задачи

Дан arreglo чисел

. it is required find такой его подотрезок

, что сумма на нём максимальна:

НаEjemplo, если бы все числа arregloа

были бы неотрицательными, то в качестве ответа можно было бы взять весь arreglo. Solución нетривиально, когда arreglo может содержать как положительные, так и отрицательные числа. Понятно, что Tarea о поиске минимального подотрезка — по сути та же самая, достаточно лишь изменить знаки всех чисел на противоположные.

Algoritmo 1

Здесь мы рассмотрим практически очевидный Algoritmo. (Дальше мы рассмотрим другой Algoritmo, который чуть сложнее придумать, однако его Implementación получается ещё короче.)

Описание Algoritmoа

Algoritmo весьма прост. Введём для удобства обозначение:

. Т.е. arreglo

— это arreglo частичных сумм

arregloа

. Также положим значение

.

Будем теперь перебирать индекс

, и научимся для каждого текущего значения

быстро

находить оптимальное

, при котором достигается максимальная сумма на подотрезке

.

Формально это означает, что нам надо для текущего

find такое

(не превосходящее

), чтобы

величина

была максимальной. После тривиального преобразования мы получаем, что нам надо

find минимальное значение в arregloе

минимум на отрезке

. Отсюда мы сразу получаем Algoritmo решения: мы просто будем хранить, где в arregloе

находится текущий

минимум. Используя этот минимум, мы за

находим текущий оптимальный индекс

, а при переходе от

текущего индекса

к следующему мы просто обновляем этот минимум.

Очевидно, этот Algoritmo работает за

и асимптотически оптимален.

Implementación

Для реализации нам даже не понадобится явно хранить arreglo частичных сумм

— от него нам будет

требоваться только текущий element. Implementación приводится в 0-индексированных arregloах, а не в 1-нумерации, как было описано выше. Приведём сначала Solución, которое находит просто численный ответ, не находя индексы искомого отрезка:

int ans = a[0],

sum = 0,

min_sum = 0;

for (int r=0; r<n; ++r) {

sum += a[r];

ans = max (ans, sum - min_sum);

min_sum = min (min_sum, sum);

} Теперь приведём полный вариант решения, который параллельно с numberвым Soluciónм находит границы искомого отрезка:

int ans = a[0],

ans_l = 0,

ans_r = 0,

sum = 0,

min_sum = 0,

min_pos = -1;

for (int r=0; r<n; ++r) {

sum += a[r];

int cur = sum - min_sum;
if (cur > ans) {

ans = cur;

ans_l = min_pos + 1;

ans_r = r;

}

if (sum < min_sum) {

min_sum = sum;

min_pos = r;

} }

Algoritmo 2

Здесь мы рассмотрим другой Algoritmo. Его чуть сложнее понять, но зато он более элегантен, чем приведённый выше, и реализуется чуть-чуть короче. Этот Algoritmo был предложен Джеем Каgivenм (Jay Kadane) в 1984 г.

Описание Algoritmoа

Сам Algoritmo выглядит следующим образом. Будем идти по arregloу и накапливать в некоторой переменной

текущую частичную сумму. Если в какой-то момент

окажется отрицательной, то мы просто присвоим

. Утверждается, что максимум из всех значений переменной

, случившихся за Tiempo de ejecución, и будет ответом

на задачу. Докажем этот Algoritmo.

В самом деле, рассмотрим первый момент времени, когда сумма

стала отрицательной. Это означает, что, стартовав

с нулевой частичной суммы, мы в итоге пришли к отрицательной частичной сумме — значит, и весь этот префикс arregloа, равно как и любой его суффикс имеют отрицательную сумму. Следовательно, от всего этого префикса arregloа в дальнейшем не может быть никакой пользы: он может дать только отрицательную прибавку к ответу. Однако этого недостаточно для доказательства Algoritmoа. В Algoritmoе мы, фактически, ограничиваемся в поиске ответа только такими отрезками, которые начинаются непосредственно после мест, когда случалось .

Но, в самом деле, рассмотрим произвольный отрезок

, причём

не находится в такой "критической" позиции (т. е.

, где

— последняя такая позиция, в которой

). Поскольку последняя критическая

позиция находится строго раньше, чем в

, то получается, что сумма

неотрицательна.

Это означает, что, сдвинув

в позицию

, мы увеличим ответ или, в крайнем случае, не изменим его. Так или иначе, но получается, что действительно при поиске ответа можно ограничиться только отрезками, начинающимися сразу после позиций, в которых оказывалось . Это доказывает правильность Algoritmoа.

Implementación

Как и в Algoritmoе 1, приведём сначала упрощённую реализацию, которая ищет только numberвой ответ, не находя границ искомого отрезка:

int ans = a[0],

sum = 0;

for (int r=0; r<n; ++r) {

sum += a[r];

ans = max (ans, sum);

sum = max (sum, 0);

} Полный вариант решения, с поддержанием индексов-границ искомого отрезка:

int ans = a[0],

ans_l = 0,

ans_r = 0,

sum = 0,

minus_pos = -1;

for (int r=0; r<n; ++r) {

sum += a[r];

if (sum > ans) {

ans = sum;

ans_l = minus_pos + 1;

ans_r = r;

}

if (sum < 0) {

sum = 0;

minus_pos = r;

} }

Смежные задачи

Поиск максимального/минимального подотрезка с Restriccionesми

Если в условии задачи на искомый отрезок

накладываются дополнительные Restricciones (наEjemplo, что

длина

отрезка должна находиться в заданных пределах), то описанный Algoritmo скорее всего легко обобщается на эти случаи — так или иначе, Tarea будет по-прежнему заключаться в поиске минимума в

arregloе

при заданных дополнительных Restriccionesх.

Двумерный случай задачи: поиск максимальной/

минимальной подматрицы

Описанная в данной статье Tarea естественно обобщается на большие размерности. НаEjemplo, в двумерном случае

она превращается в поиск такой подматрицы

заданной матрицы, которая имеет

максимальную сумму чисел в ней. Из описанного выше решения для одномерного случая легко получить Solución за

: переберём

и

,

и посчитаем arreglo сумм с

по

в каждой строке матрицы; мы пришли к одномерной задаче поиска индексов

и

в этом arregloе, которую уже можно решать за линейное время. Более быстрые Algoritmoы решения этой задачи хотя и известны, однако они не сильно быстрее

, и

при этом весьма сложны (настолько сложны, что по скрытой константе многие из них уступают тривиальному Algoritmoу при всех разумных Restriccionesх). По всей видимости, лучший из известных Algoritmoов работает

за

(T. Chan 2007 "More algorithms for all-pairs shortest paths in weighted graphs"). Этот Algoritmo Chan, а также многие другие результаты в данной области на самом деле описывают быстрое умножение матриц (где под умножением матриц подразумевается модифицированное умножение: вместо сложения используется минимум, а вместо умножения — сложение). Дело в том, что Tarea о поиске подматрицы с наибольшей суммой сводится к задаче о поиске кратчайших путей между всеми парами вершин, а эта Tarea, в свою очередь — сводится к такому умножению матриц.

Поиск подотрезка с максимальной/минимальной средней суммой

Эта Tarea заключается в том, что надо find такой отрезок

, чтобы среднее значение на нём было максимальным:

Конечно, если на искомый отрезок

по условию не наложено других условий, то Soluciónм всегда будет

являться отрезок длины

в точке-максимуме arregloа. Tarea имеет смысл, только если

имеются дополнительные Restricciones (наEjemplo, длина искомого отрезка ограничена снизу). В таком случае применим стандартный приём при работе с Tareaми о среднем значении: будем подбирать искомую максимальную среднюю величину двоичным поиском.

Для этого нам надо научиться решать такую подзадачу: given number

, и надо проверить, есть ли подотрезок arregloа

(конечно, удовлетворяющий всем дополнительным Restriccionesм задачи), на котором среднее значение больше .

Чтобы решить эту подзадачу, отнимем

от каждого elementа arregloа

. Тогда наша подTarea

фактически превращается в такую: есть или нет в данном arregloе подотрезок положительной суммы. А эту задачу мы уже умеем решать.

Таким образом, мы получили Solución за асимпотику

, где

— требуемая точность,

время решения подзадачи для arregloа длины

(которое может варьироваться в зависимости от

конкретных накладываемых дополнительных ограничений).

Solución задачи в режиме онлайн

Enunciado задачи таково: дан arreglo из

чисел, а также given number

. Поступают запросы вида

, и в ответ на

запрос it is required find подотрезок отрезка

длины не менее

с максимально возможным средним арифметическим. Algoritmo решения этой задачи достаточно сложен. Автор данного Algoritmoа — KADR (Ярослав Твердохлеб) — описал данный Algoritmo в своём сообщении на форуме.

C# solución

borrador automático, revisar antes de enviar
using System;
using System.Collections.Generic;
using System.Linq;

public static class AlgorithmDraft
{
    // Auto-generated C# draft from the original e-maxx C/C++ listing. Review before production use.
    int ans = a[0],
            sum = 0,
            min_sum = 0;
    for (int r=0; r<n; ++r) {
            sum += a[r];
            ans = max (ans, sum - min_sum);
            min_sum = min (min_sum, sum);
    }
    int ans = a[0],
            ans_l = 0,
            ans_r = 0,
            sum = 0,
            min_sum = 0,
            min_pos = -1;
    for (int r=0; r<n; ++r) {
            sum += a[r];
            int cur = sum - min_sum;
            if (cur > ans) {
                    ans = cur;
                    ans_l = min_pos + 1;
                    ans_r = r;
            }
            if (sum < min_sum) {
                    min_sum = sum;
                    min_pos = r;
            }
    }
    int ans = a[0],
            sum = 0;
    for (int r=0; r<n; ++r) {
            sum += a[r];
            ans = max (ans, sum);
            sum = max (sum, 0);
    }
    int ans = a[0],
            ans_l = 0,
            ans_r = 0,
            sum = 0,
            minus_pos = -1;
    for (int r=0; r<n; ++r) {
            sum += a[r];
            if (sum > ans) {
                    ans = sum;
                    ans_l = minus_pos + 1;
                    ans_r = r;
            }
            if (sum < 0) {
                    sum = 0;
                    minus_pos = r;
            }
    }
}

C++ solución

coincidente/original
int ans = a[0],
        sum = 0,
        min_sum = 0;
for (int r=0; r<n; ++r) {
        sum += a[r];
        ans = max (ans, sum - min_sum);
        min_sum = min (min_sum, sum);
}
int ans = a[0],
        ans_l = 0,
        ans_r = 0,
        sum = 0,
        min_sum = 0,
        min_pos = -1;
for (int r=0; r<n; ++r) {
        sum += a[r];
        int cur = sum - min_sum;
        if (cur > ans) {
                ans = cur;
                ans_l = min_pos + 1;
                ans_r = r;
        }
        if (sum < min_sum) {
                min_sum = sum;
                min_pos = r;
        }
}
int ans = a[0],
        sum = 0;
for (int r=0; r<n; ++r) {
        sum += a[r];
        ans = max (ans, sum);
        sum = max (sum, 0);
}
int ans = a[0],
        ans_l = 0,
        ans_r = 0,
        sum = 0,
        minus_pos = -1;
for (int r=0; r<n; ++r) {
        sum += a[r];
        if (sum > ans) {
                ans = sum;
                ans_l = minus_pos + 1;
                ans_r = r;
        }
        if (sum < 0) {
                sum = 0;
                minus_pos = r;
        }
}

Java solución

borrador automático, revisar antes de enviar
import java.util.*;
import java.math.*;

public class AlgorithmDraft {
    // Auto-generated Java draft from the original e-maxx C/C++ listing. Review before production use.
    int ans = a[0],
            sum = 0,
            min_sum = 0;
    for (int r=0; r<n; ++r) {
            sum += a[r];
            ans = max (ans, sum - min_sum);
            min_sum = min (min_sum, sum);
    }
    int ans = a[0],
            ans_l = 0,
            ans_r = 0,
            sum = 0,
            min_sum = 0,
            min_pos = -1;
    for (int r=0; r<n; ++r) {
            sum += a[r];
            int cur = sum - min_sum;
            if (cur > ans) {
                    ans = cur;
                    ans_l = min_pos + 1;
                    ans_r = r;
            }
            if (sum < min_sum) {
                    min_sum = sum;
                    min_pos = r;
            }
    }
    int ans = a[0],
            sum = 0;
    for (int r=0; r<n; ++r) {
            sum += a[r];
            ans = max (ans, sum);
            sum = max (sum, 0);
    }
    int ans = a[0],
            ans_l = 0,
            ans_r = 0,
            sum = 0,
            minus_pos = -1;
    for (int r=0; r<n; ++r) {
            sum += a[r];
            if (sum > ans) {
                    ans = sum;
                    ans_l = minus_pos + 1;
                    ans_r = r;
            }
            if (sum < 0) {
                    sum = 0;
                    minus_pos = r;
            }
    }
}

Материал разбит как Algoritmoическая Tarea: изучить постановку, понять асимптотику и реализовать Algoritmo на выбранном языке.

Vacantes para esta tarea

Se muestran vacantes activas con etiquetas coincidentes.

Todas las vacantes
Todavía no hay vacantes activas.