Оптимизация Кнута в динамическом программировании
Оптимизация Кнута — очень мощный инструмент динамического программирования, который можно использовать для уменьшения временной сложности решений в основном с O(N 3 ) до O(N 2 ) . Обычно он используется для задач, которые можно решить с помощью диапазона DP при выполнении определенных условий. В этой статье мы сначала исследуем саму оптимизацию, а затем решим с ее помощью задачу.
Критерии оптимизации:
Оптимизация Кнута применяется к задачам ДП с переходом следующего вида –
dp[i][j] = cost[i][j] + mini≤k<j(dp[i][k] + dp[k+1][j])
Кроме того, функция стоимости должна удовлетворять следующим условиям (для a ≤ b ≤ c ≤ d) —
- Это монотон на решетке интервалов (MLI) [стоимость (b, c) ≤ стоимость (a, d)]
- Он удовлетворяет четырехугольному неравенству (QI) [стоимость (a, c) + стоимость (b, d) ≤ стоимость (b, c) + стоимость (a, d)]
Оптимизация:
Для решения, которое выполняется за время O(N 3 ) , мы перебираем все значения k от i до j-1 . Однако мы можем добиться большего, используя следующую оптимизацию:
- Определим еще один массив в дополнение к массиву dp — opt[N][N] . Определите opt[i][j] как максимальное (или минимальное) значение k , для которого dp[i][j] минимизируется при переходе dp.
opt[i][j] = argmin i≤k<j (dp[i][k] + dp[k+1][j]) - Ключом к оптимизации Кнута и нескольким другим оптимизациям в DP, таким как оптимизация «разделяй и властвуй» (не путать с алгоритмом «разделяй и властвуй», описанным в этой статье), является следующее неравенство:
opt[i][j-1] ≤ opt[i][j] ≤ opt[i+1][j]
- Это помогает, потому что теперь для каждого перехода dp[] для вычисления dp[i][j] нам нужно только проверить значения k между opt[i][j-1] и opt[i+1][j] вместо этого между i к j-1 . Это снижает временную сложность алгоритма в линейный раз, что является значительным улучшением.
Доказательство правильности:
Чтобы доказать правильность этого алгоритма, нам нужно только доказать неравенство :
opt[i][j-1] ≤ opt[i][j] ≤ opt[i+1][j].
Следуйте приведенному ниже разделу для доказательства правильности алгоритма:
Assumptions: If cost(i, j) is an MLI and satisfies the Quadrangle Inequality, then dp[i][j] also satisfies the inequality.
Now, consider the following setup –
- For i < j, we have some indices i ≤ p ≤ q < j-1.
- Let dpk[i][j] = cost[i][j] + dp[i][k] + dp[k+1][j].
If we can show that:
dpp[i][j-1] ≥ dpq[i][j-1] ⇒ dpp[i][j] ≥ dpq[i][j]
then setting q = opt[i][j-1], it will be clear that opt[i][j] will be at least as much as opt[i][j-1], due to the implication of the above inequality for all indices p less than opt[i][j-1].
This will prove that opt[i][j-1] ≤ opt[i][j].Proof: Using the quadrangle inequality on the dp array, we get –
dp[p+1][j-1] + dp[q+1][j] ≤ dp[q+1][j-1] + dp[p+1][j]
⇒ (dp[i, p] + dp[p+1][j-1] + cost(i, j-1)) + (dp[i, q] + dp[q+1][j] + cost(i, j))
≤ (dp[i, q] + dp[q+1][j-1] + cost(i, j-1)) + (dp[i, p] + dp[p+1][j] + cost(i, j))
⇒ dpp[i][j-1] + dpq[i][j] ≤ dpp[i][j] + dpq[i][j-1]
⇒ dpp[i][j-1] – dpq[i][j-1] ≤ dpp[i][j] – dpq[i][j]dpp[i][j-1] ≥ dpq[i][j-1]
⇒ 0 ≤ dpp[i][j-1] – dpq[i][j-1] ≤ dpp[i][j] – dpq[i][j]
⇒ dpp[i][j] ≥ dpq[i][j].Hence it is proved that: dpp[i][j-1] ≥ dpq[i][j-1] ⇒ dpp[i][j] ≥ dpq[i][j]
The second part of the inequality (i.e. opt[i][j] ≤ opt[i+1][j]) can be shown with the same idea, starting with the inequality
dp[i][p]+dp[i+1][q] ≤ dp[i][q]+dp[i+1][p].
The proof of these two inequalities combinedly gives: opt[i][j-1] ≤ opt[i][j] ≤ opt[i+1][j]
Пример:
Для заданного массива arr[] из N элементов задача состоит в том, чтобы найти минимальную стоимость сокращения массива до одного элемента за N-1 операций, где в каждой операции:
- Удалить элементы с индексами i и i+1 для некоторого допустимого индекса i , заменив их их суммой.
- Стоимость этого составляет arr[i] + arr[i+1] , где arr[] — это состояние массива непосредственно перед операцией.
- Эта стоимость будет добавлена к окончательной стоимости.
Примеры:
Input: arr[] = {3, 4, 2, 1, 7}
Output: 37
Explanation:
Remove the elements at 0th and 1st index. arr[] = {7, 2, 1, 7}, Cost = 3 + 4 = 7
Remove 1st and 2nd index elements. arr[] = {7, 3, 7}, Cost = 2 + 1 = 3
Remove 1st and 2nd index elements, arr[] = {7, 10}, Cost = 3 + 7 = 10
Remove the last two elements. arr[] = {17}, Cost = = 7 + 10 = 17
Total cost = 7 + 3 + 10 + 17 = 37
This is the minimum possible total cost for this array.Input: arr[] = {1, 2, 3, 4}
Output: 19
Explanation:
Remove the 0th and 1st index elements. arr[] = {3, 3, 4}. Cost = 1 + 2 = 3
Remove the 0th and 1st index elements. arr[] = {6, 4}. Cost = 3 + 3 = 6
Remove the 0th and 1st index elements. arr[] = {10}. Cost = 6 + 4 = 10
Total cost = 3 + 6 + 10 = 19.
This is the minimi=um possible cost.
Неоптимальное решение (с использованием Range DP): проблему можно решить, используя следующую идею:
- Let arr[] be the original array before any modifications are made.
- For an element in the array that has been derived from indices i to j of a[], the cost of the final operation to form this single element will be the sum arr[i] + arr[i+1] + . . . + arr[j]. Let this value be denoted by the function cost(i, j).
- To find the minimum cost for the section arr[i, i+1, … j], consider the cost of converting the pairs of sub-arrays arr[i, i+1 . . . k] & arr[k+1, k+2 . . . j] into single elements, and choose the minimum over all possible values of k from i to j-1 (both inclusive).
Для реализации вышеуказанной идеи:
- Функцию стоимости можно рассчитать за постоянное время с предварительной обработкой, используя массив суммы префиксов:
- Вычислить сумму префикса (скажем, сохраненную в массиве pref[]).
- Таким образом, стоимость (i, j) может быть рассчитана как (pref[j] – pref[i-1]).
- Переход от i = 0 до N-1:
- Перейдите от j = i+1 до N-1 , чтобы сгенерировать все подмассивы основного массива:
- Решите эту проблему для всех этих возможных подмассивов со следующим переходом dp —
dp[i][j] = cost(i, j) + min i≤k≤j-1 (dp[i][k] + dp[k+1][j]) , как объяснено в приведенной выше идее.
- Решите эту проблему для всех этих возможных подмассивов со следующим переходом dp —
- Перейдите от j = i+1 до N-1 , чтобы сгенерировать все подмассивы основного массива:
- Здесь dp[i][j] — минимальная стоимость применения (j – i) операций над подмассивом arr[i, i+1, . . . дж] чтобы преобразовать его в один элемент.
стоимость (i, j) обозначает стоимость последней операции, т. е. стоимость добавления двух последних значений для преобразования arr[i, i+1, . . ., j] к одному элементу. - Окончательный ответ будет сохранен на dp[0][N-1] .
Ниже приведена реализация описанного выше подхода.
Временная сложность: O(N 3 )
Вспомогательное пространство: O(N 2 )
Оптимальное решение (с использованием оптимизации Кнута) :
Для этого вопроса выполняются условия применения оптимизации Кнута:
cost(i, j) is merely the sum of all elements in the subarray bounded by the indices i and j. Further, the transition function of dynamic programming matches the general case for applying this technique.
Выполните шаги, упомянутые здесь, чтобы реализовать идею оптимизации Кнута:
- Определите массив opt[N][N] и массив dp[][] .
- Теперь обработайте подмассивы в порядке возрастания длины с начальным состоянием dp[i][i] = 0 и opt[i][i] = i (поскольку значение должно быть i , чтобы минимизировать значение dp[i][i][ я] ).
- Таким образом, когда мы достигаем подмассива arr[i, . . ., j] , значение opt[i][j-1] и выберите [i+1][j] уже известны. Итак, проверяем только условие opt[i][j-1] ≤ k ≤ opt[i+1][j] , найти opt[i][j] и dp[i][j] .
- Здесь также функция стоимости рассчитывается так же, как и в предыдущем подходе, с использованием массива сумм префиксов .
Примечание. В приведенном ниже коде используется другой способ перебора всех подмассивов (вместо цикла в порядке возрастания длины). Однако, гарантируя, что opt[i+1][j] и opt[i][j- 1] вычисляются до dp[i][j].
Ниже приведена реализация описанного выше подхода.
Временная сложность: O(N 2 )
Вспомогательное пространство: O(N 2 )