Алгоритм поиска наименьшей цены для прохода через массив

Я много думал о следующей проблеме:

Нам дан массив из n чисел. Мы начинаем с первого индекса, и наша задача — добраться до последнего индекса. При каждом шаге мы можем перейти на один или два шага вперед, и число в индексе, к которому мы переходим, представляет стоимость, которую нам нужно заплатить за посещение этого индекса. Нам нужно найти самый дешевый способ добраться до конца массива.

Например, если массив выглядит так: [2,1,4,2,5] самый дешевый способ добраться до конца — 10: мы посещаем индексы 1->2- >4->5 и мы должны заплатить 2+1+2+5 = 10, что является самым дешевым способом. Пусть f(i) будет самой низкой ценой для достижения индекса i. Это мы можем легко вычислить за время O(n) с помощью динамического программирования, поняв, что f(i) = arr[i] + min(f(i-1),f(i- 2))

Но вот в чем загвоздка: массив обновляется несколько раз, и после каждого обновления мы должны быть в состоянии определить за время O(logn), какой способ на данный момент является самым дешевым. Обновление массива происходит путем указания индекса, который будет изменен, и номера, на который он будет изменен. Например, обновление может быть следующим: arr[2] = 7, изменив наш примерный массив на [2,7,4,2,5]. Теперь самый дешевый способ будет 11.

Как мы можем поддерживать эти обновления за время O(logn)? Любые идеи?

Вот что я придумал: сначала я бы создал массив f для динамического программирования, как описано выше. Я бы сохранил содержимое этого массива в дереве сегментов s следующим образом: s(i) = f(i) - f(i-1). Это позволило бы мне обновлять интервалы f (добавляя константу к каждому значению) за время O(logn) и запрашивать значения по заданному индексу в O(logn) раз. Это может пригодиться, так как после некоторых обновлений часто бывает так, что все значения в f должны быть увеличены на константу после некоторого заданного индекса в f. Таким образом, запрашивая значение s(n) в дереве сегментов после каждого обновления, мы получим нужный нам ответ.

Однако есть разные вещи, которые могут произойти после обновления:

  1. Необходимо обновить только одно значение в f. Например, если [2,1,4,2,5,4,9,3,7] изменить на [2,1,9,2,5,4,9, 3,7] нужно будет обновить только f(3), так как ни один из самых дешевых способов не проходит через индекс 3. в любом случае.
  2. Все значения в f после заданного индекса должны быть обновлены константой. Для этого и подходит дерево сегментов.
  3. Каждое другое значение в f после заданного индекса должно обновляться константой.
  4. Что-то более случайное.

person tykkipeli    schedule 27.09.2018    source источник
comment
@SamerTufail Вам нужно будет доказать, что не может быть обновления, которое создает новый кратчайший путь, не касаясь ни одного узла на предыдущем кратчайшем пути.   -  person fjardon    schedule 27.09.2018
comment
@fjardon спасибо, я пропустил эту часть.   -  person Samer Tufail    schedule 27.09.2018
comment
Не могли бы вы дать ссылку на исходную проблему?   -  person Pham Trung    schedule 27.09.2018
comment
Конечно, но изначальная проблема была на моем родном языке на финском, так что вы не поймете слишком много. cses.fi/alon18/task/1247 Здесь вы можете найти ограничения и примеры входных данных и выходы однако   -  person tykkipeli    schedule 27.09.2018
comment
В исходном вопросе не упоминается log n временная сложность. Вы сами пришли к выводу, что это можно сделать за логарифмическое время?   -  person meowgoesthedog    schedule 27.09.2018
comment
Ну, все, что значительно медленнее, чем O(logn), недостаточно быстро. Размер массива в тестах 200000 и есть 200000 обновлений   -  person tykkipeli    schedule 27.09.2018
comment
Я думаю, что производительность должна зависеть не только от n, но и от измененного числа. если бы 1 было изменено на 10 ^ 6, вероятно, потребовалось бы больше времени, чем если бы 1 было изменено на 2.   -  person Yola    schedule 27.09.2018
comment
Числа в массиве могут быть любыми от 1 до 10^9, и они могут меняться с любого числа на любое другое число. Да, действительно, производительность некоторых (плохих) алгоритмов сильно зависит от того, что и как было изменено. Я получил подсказку от надежного источника (создателя проблемы), что после каждого обновления новая минимальная стоимость действительно может быть вычислена за время O(logn), и это не зависит от того, что изменилось или сколько.   -  person tykkipeli    schedule 27.09.2018
comment
Эту проблему можно решить с помощью динамизации Дейкстры. существует менее сложное решение.   -  person Pham Trung    schedule 28.09.2018


Ответы (1)


Хорошо, мне удалось решить проблему самостоятельно, поэтому я решил поделиться решением с вами. :)

Я был на правильном пути с динамическим программированием и деревом сегментов, но в своих предыдущих попытках я неправильно кормил дерево сегментов.

Вот как мы можем поддерживать обновления за время O(logn): Идея состоит в том, чтобы использовать двоичное дерево сегментов, где листья дерева представляют текущий массив, а каждый узел хранит 4 разных значения.

  1. v1 = минимальная стоимость перехода от самого левого потомка к самому правому потомку
  2. v2 = минимальная стоимость перехода от самого левого потомка к второму самому правому потомку
  3. v3 = наименьшая стоимость перехода от второго крайнего левого потомка к крайнему правому потомку
  4. v4 = минимальная стоимость перехода от второго крайнего левого потомка к второму крайнему правому потомку

Под потомками я подразумеваю потомков узла, которые также являются листьями.

При обновлении массива мы обновляем значение листа, а затем всех его предков до корня. Поскольку в каждом узле мы уже знаем все эти 4 значения двух его дочерних элементов, мы можем легко вычислить новые 4 значения для текущего родительского узла. Просто для примера: v1_current_node = min(v2_leftchild+v1_rightchild, v1_leftchild+v1_rightchild, v1_leftchild+v3_rightchild). Аналогично рассчитываются все остальные три значения.

Поскольку для каждого листа существует только O(logn) предков, а все 4 значения вычисляются за время O(1), требуется всего O(logn)< /strong> время обновить все дерево.

Теперь, когда мы знаем 4 значения для каждого узла, мы можем аналогичным образом вычислить наименьшую стоимость от первого до n:го узла, используя узлы наивысшие степени 2 на нашем пути в дереве, которые в сумме дают n. Например, если n = 11, мы хотим узнать наименьшую стоимость от первого до одиннадцатого узла, и это можно сделать, используя информацию об узле, который покрывает листья 1-8. >, узел, покрывающий листья 9–10, и узел-лист 11. Для всех этих трех узлов мы знаем описанные значения 4 и можем аналогичным образом объединить эту информацию, чтобы найти ответ. В лучшем случае нам нужно рассмотреть узлы O(logn) для этого, так что это не проблема.

person tykkipeli    schedule 28.09.2018
comment
Если у нас есть 16 элементов, например, второй уровень от листьев знает элементы [1-4, 5-8, 9-12, 13-16] , следующий уровень знает [1-8, 9-16]. Но это непересекающиеся множества. Если мы знаем кратчайшую стоимость от 1 до 7 или 8 и обновляем один из листьев между 1 и 8, узел, хранящий информацию о 9-16, не обновляется. Но как мы можем узнать цену достижения 9, не зная стоимости 7 или 8? Дерево будет предоставлять неточную информацию, если обновления будут только по непересекающемуся множеству. Пожалуйста, объясните, как это решается, если я неправильно понимаю. - person גלעד ברקן; 28.09.2018
comment
Вы читали мой пример, как рассчитать v1? Вы понимаете, почему эта формула верна? Да, есть узел, отвечающий за 1-8, а также узел, отвечающий за 9-16. Теперь предположим, что вы уже получили все 4 значения для обоих этих дочерних узлов. Теперь, как мы можем вычислить 4 значения для узла, ответственного за dor 1-16? Допустим, теперь мы хотим узнать значение v1 для этого узла, то есть путь с наименьшей стоимостью от листа 1 до листа 16. - person tykkipeli; 28.09.2018
comment
Теперь есть три возможных пути, по которым это может быть. Либо мы выбираем самый дешевый путь от 1 до 7 (т. самый дешевый путь от 1 до 8, перейти к 9 и выбрать самый дешевый путь от 9 до 16 ИЛИ мы выберем самый дешевый путь от 1 до 8, перейти к 10 и затем выбрать самый дешевый путь от 10 до 16. Самый дешевый путь от 1 до 16 со 100% уверенностью является одним из этих путей, поэтому мы просто берем минимум. - person tykkipeli; 28.09.2018
comment
Итак, мы говорим, что затраты являются на самом деле непересекающимися. Мы можем использовать сегменты пути и соединять их, не завися от более ранних сегментов. Интересно. Я подумаю об этом, спасибо. - person גלעד ברקן; 28.09.2018