Предполагается, что вы уже знакомы с базовыми понятиями динамического программирования и помните бинарный поиск.
- Задача о рюкзаке
- НВП
- НОП
- Динамика по префиксу и значению последнего элемента
- Ленивая динамика
В самой простой форме задача о рюкзаке формулируется так:
Даны
$n$ предметов с весами$a_1,\ldots,a_n$ . Определите, на какой максимальный вес можно набрать предметов в рюкзак вместимости$W$ .
Для решения этой задачи воспользуемся динамическим программированием. Обозначим за
Для каждого состояния
dp[0][0] = True
for i in range(1, n + 1):
for j in range(0, W + 1):
dp[i][j] = dp[i - 1][j]
if a[i] <= j and dp[i - 1][j - a[i]]:
dp[i][j] = True
Ответом будет максимальное
Немного усложним задачу. Пусть, теперь предметы имеют не только веса, но и стоимости
for i in range(1, n + 1):
for j in range(0, W + 1):
dp[i][j] = dp[i - 1][j]
if a[i] <= j:
dp[i][j] = max(dp[i][j], dp[i - 1][j - a[i]] + c[i])
Ответом на задачу будет максимальное
Если так получилось, что веса большие, а стоимости маленькие, можно поменять их местами и считать минимальный вес при данной набранной стоимости. Поменять местами значение динамики и параметр — довольно распространенный трюк в динамическом программировании.
Пусть, теперь предметов каждого типа может быть несколько, то есть даны не только веса и стоимости, но и максимальные количества каждого из предметов
for i in range(1, n + 1):
for j in range(0, W + 1):
for cnt in range(min(k[i], W // a[i]) + 1):
if a[i] * cnt <= j:
dp[i][j] = max(dp[i][j], dp[i - 1][j - a[i] * cnt] + c[i] * cnt)
Такое решение работает за
Можете попробовать решить эту задачу за
Пусть, теперь каждого предмета будет не
for i in range(1, n + 1):
for j in range(0, W + 1):
dp[i][j] = dp[i - 1][j]
if a[i] <= j:
dp[i][j] = max(dp[i][j], dp[i][j - a[i]] + c[i])
Такое решение работает за
Если
Во всех рассмотренных задачах восстановление ответа делается стандартным образом: нужно из ответа пройтись обратно до начала.
- первый способ - это построить массив prev, где для каждой ячейки
$dp[i][j]$ хранить, берем мы предмет i, или не берем предмет$i$ . - второй способ - это определять это на ходу, смотря, какое из значений
$dp[i - 1][j]$ или$dp[i - 1][j - a[i]] + c[i]$ больше.
Пусть, дана последовательность из
Пример: в последовательности
Давайте решать наивно через динамческое программирование - то есть хранить в
Ну, есть два варианта:
-
$i$ -ое число не входит в НВП. Тогда$dp[i] = 1$ -
$i$ -ое число входит в НВП. Тогда$dp[i] = 1 + dp[k]$ , где$k$ - индекс предыдущего числа в этой НВП. Так давайте просто его переберем. При этом надо учесть, что$a[k]$ должно быть меньше, чем$a[i]$ !
Итогвоая формула получается такая:
Этот алгоритм работает за
Ответ восстанавливается тем же способом: для каждого состояния нужно сохранить, где был этот максимум - там и есть предыдущее число в НВП.
Решим эту задачу чуть более нестандартным динамическим программированием, где
Изначально
Рассматривая очередной элемент, попробуем продлить им каждую подпоследовательность:
n = 6
a = [6, 1, 5, 3, 4, 2] # НВП: 1, 3, 4
inf = 100
min_end = [-inf] + [inf] * n
for i in range(n):
for j in range(n):
if min_end[j - 1] < a[i] < =min_end[j]:
min_end[j] = a[i]
print(dp)
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-1-930ff2a8d8b0> in <module>()
7 if min_end[j - 1] < a[i] < min_end[j]:
8 min_end[j] = a[i]
----> 9 print(dp)
NameError: name 'dp' is not defined
Ответом будет максимальный такой индекс
Его можно значительно ускорить, заметив два факта:
- На любом шаге
$min_end[i-1]\leq min_end[i]$ . Это легко доказать от противного. - Из предыдущего факта следует, что любое
$a[i]$ обновит максимум одно значение динамики, так как попадет максимум в один интервал.
Значит, для поиска
Даны две последовательности
Решим эту задачу с помощью динамического программирования, где
Тогда заметим, что есть две ситуации, когда мы считаем
-
$a_i \neq b_j$ , тогда хотя бы один из этиз символов не содержится в НОП, иначе она заканчивается на два разных символа. В этом случае$dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])$ -
$a_i = b_j$ , тогда несложно доказать, что точно есть максимальная НОП, в которую входят ОБА этих символа, а значит$dp[i][j] = 1 + dp[i - 1][j - 1]$ .
А на пустых префиксах ответ 0.
a = [1, 100, 2, 100, 3]
b = [10, 10, 1, 2, 3, 10] # НОП: 1,2,3
n = len(a)
m = len(b)
dp = [[0 for j in range(m + 1)] for i in range(n + 1)]
for i in range(1, n + 1):
for j in range(1, m + 1):
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])
if a[i - 1] == b[j - 1]:
dp[i][j] = max(dp[i][j], dp[i - 1][j - 1] + 1)
print(dp[i])
[0, 0, 0, 1, 1, 1, 1]
[0, 0, 0, 1, 1, 1, 1]
[0, 0, 0, 1, 2, 2, 2]
[0, 0, 0, 1, 2, 2, 2]
[0, 0, 0, 1, 2, 3, 3]
Ответом является максимальное число в массиве
Ответ при это восстанавливается классическим способом - с конца. Нам все еще нужно просто в каждой ячейке смотреть - если символы в ней равны, то нужно уменьшить
Сведите задачу НВП к задаче НОП.
Найдите НОП двух перестановок длины
Пусть, дана последовательность
Соответственно для каждого
for i in range(1, n + 1):
dp[a[i]] += 1
if a[i] > 0:
dp[a[i]] = max(dp[a[i]], dp[a[i] - 1] + 1)
if a[i] < A:
dp[a[i]] = max(dp[a[i]], dp[a[i] + 1] + 1)
Это решение за
Заметим, что вот эти две идеи встречаются в задачах наиболее часто:
- хранить в
$dp[i]$ ответ для$i$ -ого префикса. Как в рюкзаке (где можно пользоваться$i$ первыми предметами), НВП(где ответ на префиксе длины$i$ ) и НОП (где ответ для префиксов длины$i$ и$j$ ). - хранить в
$dp[i]$ ответ для последовательностей, заканчивающихся на$i$ .
Если сложно придумать порядок обхода таким образом, чтобы все предыдущие значения уже были посчитаны, то можно вместо циклов использовать рекурсивную функцию и запоминать посчитанный результат, чтобы не считать несколько раз одно и то же.
Решим, например, обычную задачу о рюкзаке таким образом. Изначально все
def calc(i, j):
if dp[i][j] == -1:
dp[i][j] = calc(i - 1, j)
if a[i] <= j:
dp[i][j] = max(dp[i][j], calc(i - 1, j - a[i]) + c[i])
return dp[i][j]
answer = 0
for j in range(W + 1):
answer = max(answer, calc(n, j))
Время работы так же составит
Решите как можно больше задач из этих двух контестов:
https://informatics.msk.ru/mod/statements/view.php?id=35888
https://informatics.msk.ru/mod/statements/view.php?id=33257
https://csacademy.com/contest/round-61/task/strictly-increasing-array/statement/