Блог пользователя seiko.iwasawa

Автор seiko.iwasawa, 3 года назад, По-русски

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

Пусть $$$w(j, i)$$$ — некоторая функция стоимости, которую мы умеем вычислять за $$$O(1)$$$ и для которой выполняется неравенство четырехугольника:

$$$w(a, c) + w(b, d) \le w(a, d) + w(b, c)$$$, где $$$a \le b \le c \le d$$$.

Наша задача: посчитать динамику вида $$$dp[i] = \min\limits_{0 \le j < i} dp[j] + w(j, i)$$$ (будем считать, что $$$dp[0] = 0$$$) быстрее, чем за $$$O(n^2)$$$, где $$$n$$$ — размер нашей динамики.

Примеры функций стоимости

Теория

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

$$$tmp[i][j] = \min\limits_{0 \le x \le j} dp[x] + w(x, i)$$$; ($$$j < i$$$).

По сути, $$$tmp[i][j]$$$ — это просто промежуточное состояние $$$dp[i]$$$, когда мы его прорелаксировали только через первые $$$(j+1)$$$ индексов. Таким образом, например, $$$tmp[i][i - 1] = dp[i]$$$.

Теперь давайте введем следующую функцию:

$$$k(j, i) = \arg \min\limits_{0 \le x \le j} dp[x] + w(x, i)$$$; ($$$j < i$$$).

Другими словами, $$$k(j, i)$$$ является точкой разреза (точкой оптимума) для $$$tmp[i][j]$$$ — минимальным индексом $$$0 \le x \le j$$$ таким, что $$$tmp[i][j] = dp[x] + w(x, i)$$$.

Заметим, что если мы будет знать значение $$$k(i - 1, i)$$$, то мы сможем за $$$O(1)$$$ посчитать значение $$$dp[i]$$$:

$$$dp[i] = dp[k(i - 1, i)] + w(k(i - 1, i), i)$$$.

Также заметим, что мы можем посчитать значение $$$k(j + 1, i)$$$ через $$$k(j, i)$$$:

$$$ k(j + 1, i)= \begin{cases} j + 1, &\text{если $$$(j + 1)$$$ - точка разреза для $$$tmp[i][j+1]$$$}\\ k(j, i), &\text{иначе} \end{cases} $$$

То есть чтобы вычислить значение $$$k(j + 1, i)$$$ через $$$k(j, i)$$$, достаточно просто проверить, выполняется ли неравенство $$$dp[j + 1] + w(j + 1, i) < dp[k(j, i)] + w(k(j, i), i)$$$.

Утверждение. Для любого фиксированного $$$j$$$ функция $$$k_j(i) = k(j, i)$$$ является неубывающей для $$$i=(j+1)..n$$$.

Доказательство

Следствие. Если $$$k_{j+1}(i)=j+1$$$, то из-за монотонности $$$k_{j+1}(i+1) \ge j+1 \Rightarrow k_{j+1}(i+1) = j+1$$$. Поскольку $$$k_{j+1}(i)=j+1$$$ или $$$k_{j+1}(i)=k_j(i)$$$, то значения у $$$k_{j+1}$$$ будут такими же, как и у $$$k_j$$$, за исключением некоторого суффикса (возможно, пустого) — значения на нем будут равны $$$(j+1)$$$.

Тогда мы можем легко вычислять все значения $$$k(j, i)$$$. Значения для $$$k(0, i)$$$ мы знаем — они все равны 0. Допустим, что все значения $$$k(x, i)$$$ для $$$x \le j$$$ мы посчитали. Чтобы посчитать значения для $$$k(j + 1, i)$$$, посчитаем значение динамики для $$$dp[j + 1]$$$ (мы можем это сделать за $$$O(1)$$$, так как $$$k(j, j + 1)$$$ уже посчитано), найдем суффикс, на котором все значения будут равны $$$(j+1)$$$ (его можно найти, например, с помощью бинпоиска, проверяя, чему равно значение текущего $$$k(j + 1, mid)$$$), "скопируем" значения $$$k(j, i)$$$ и сделаем присвоение к $$$(j+1)$$$ на суффиксе. О том, как делать это эффективно поговорим в разделе Алгоритм. Заметим, что кроме подсчета $$$k(j, i)$$$ мы также нашли и все значения $$$dp[i]$$$, что от нас и требовалось.

Пример работы алгоритма

Алгоритм

Будем хранить только для текущего $$$j$$$ значения $$$k(j, i)$$$ в виде дека отрезков равных значений (для каждого отрезка мы будем хранить значения $$$k(j, i)$$$ на нем и его начало). Для того, чтобы посчитать $$$dp[j+1]$$$, мы должны взять просто значение $$$k(j, j + 1)$$$ из дека — оно лежит в самом первом отрезке, так как $$$(j + 1)$$$ — минимальное возможное значение $$$i$$$ для фукнции $$$k_j$$$. Теперь вместо "копирования" мы просто удалим первый элемент из первого отрезка: увеличим значение начала первого отрезка на один. Если отрезок стал пустым, то удалим его. Далее нам нужно найти суффикс, на котором значения функции обновятся. Заметим, что у нас из дека удалится несколько отрезков из конца дека (возможно, все или ни одного). Чтобы проверить, нужно ли удалять последний отрезок из дека, нужно проверить, изменилось ли значение фукнции на $$$(j + 1)$$$ в начале отрезка. Будем удалять отрезок из конца дека, пока дек не пустой и утверждение верно. Далее с помощью бинпоиска находим начало нового отрезка и добавляем его в дек (крайними случаями будут, если дек пустой или длина нового отрезка равна 0). Для некоторых задач мы можем без бинпоиска найти начало отрезка — это улучшит асимптотику. Для этого нам нужно уметь считать функцию $$$D(j, i)$$$ — минимальное значение $$$x$$$ такое, что $$$dp[j] + w(j, x) \ge dp[i] + w(i, x)$$$.

Асимптотика

Пример реализации можно найти ниже или здесь (но там рассматривается немного другая реализация).

Пример

Рассмотрим задачу 319C - Калила и Димна на лесозаготовках.

Эту задачу можно свести к следующему подсчету динамики:

$$$dp[i]=\min\limits_{j<i} dp[j] + b[j] \cdot a[i]$$$,

Причем для каждого $$$i < j$$$ верно, что $$$a[i] < a[j]$$$ и $$$b[i] > b[j]$$$.

Введем тогда следующую функцию стоимости:

$$$w(j, i) = b[j] \cdot a[i]$$$.

Заметим, что для нее выполняется неравенство четырехугольника по транснеравентсву, поэтому мы можем применить 1D1D-оптимизацию.

Пример возможной реализции за $$$O(n\log{n})$$$: 125522308.

Чтобы написать решение за линию, можно искать для функции $$$D(j, i)$$$ не минимальное значение индекса, а минимальное число $$$x$$$ такое, что $$$dp[j] + b[j] \cdot x > dp[i] + b[i] \cdot x$$$. Тогда для дека мы также будем хранить тогда не начало-индекс, а начало-число. Заметим, что работа именно с $$$a[i]$$$ нам нужна только для подсчета значений динамики $$$dp[i]$$$, поэтому мы можем немного изменить наш алгоритм, сказав, что мы не будем уменьшать никакие длины отрезков из начала, а будем удалять отрезки из начала, пока они левее на числовой прямой, чем текущее $$$a[j]$$$. Значение $$$D(j, i)$$$ легко посчитать — оно равно $$$\frac{dp[i] - dp[j]}{b[j] - b[i]}$$$.

Пример возможной реализации за $$$O(n)$$$: 125523046.

Источники: 1 2.

  • Проголосовать: нравится
  • +98
  • Проголосовать: не нравится

»
3 года назад, # |
  Проголосовать: нравится +28 Проголосовать: не нравится

Круто! Спасибо. А то вроде все говорят про 1D-1D, а никто до конца и не понимает, что это.

»
2 года назад, # |
  Проголосовать: нравится +3 Проголосовать: не нравится

I've always called this Convex Hull Trick of a function that's not linear :thinking:

»
2 года назад, # |
Rev. 2   Проголосовать: нравится +23 Проголосовать: не нравится

Great blog!

While I was reading it I believe I caught a typo.

In this part:

$$$k(j, i) = \arg \min\limits_{0 \le x \le j} \left( dp[x] + {\bf w(x, j)} \right)$$$

In other words, $$$k(j,i)$$$ is an optimum index for relaxing $$$tmp[i][j]$$$; it is the index $$$0\leq x\leq j$$$ such that $$$tmp[i][j]=dp[x]+{\bf w(x,j)}$$$.

The two bold $$$w(x,{\bf j})$$$ should be $$$w(x, {\bf i})$$$ if I am not mistaken.