Основы
Понятие динамического программирования похоже на понятие индукции в
математике. Решить задачу - значит свести ее к похожим задачам, которые чуть-чуть
проще, и их решить тем же способом.
Самый простой пример: задача про кузнечика. Кузнечик стоит на 0 ступеньке,
за ход прыгает на 1 или 2 ступеньки, надо найти число способов дойти до ступеньки N.
Здесь нужно догадаться, что задача “сколько способов дойти до N” напрямую
сводится к задачам “сколько способов дойти до N-1” и “сколько способов дойти до N-
2”. Поэтому надо завести массив dp[i], где dp[i] - число способов дойти до ступеньки i.
Тогда dp[0] = 1, dp[1] = 1, и верна формула dp[i] = dp[i - 1] + dp[i - 2], так как любой путь
до i-й вершины последним ходом либо прыгает на один (таких путей до
предпоследней ступеньки dp[i - 1]), либо на два (таких dp[i - 2]).
Осталось просто завести массив, заполнить его начальные значения, и
посчитать слева направо по формуле все остальные значения до N:
dp = [0] * (n + 1)
dp[0] = 1
dp[1] = 1
for i in range(2, n + 1):
dp[i] = dp[i - 1] + dp[i - 2]
print(dp[n])
НВП
Возьмем такую задачу: надо найти наибольшую возрастающую
подпоследовательность (НВП) в массиве. Подпоследовательность - это то, что
остается, после зачеркивания некоторых значений в массиве.
Например, в массиве [4, 2, 3, 6, 8, 5, 6, 10, 7, 2, 1, 9] жирным выделена
максимальная возрастающая подпоследовательность.
Эта задача не решается динамикой за O(N), к сожалению, хотя сама задача и
про массив.
Попытаемся свести задачу к меньшей. Что, если мы выкинем последний
элемент массива и узнаем в оставшемся НВП? И если в конец добавляется последний
элемент (он больше последнего в НВП), то добавим его. Действительно, это работает.
Но если он меньше и он не добавляется, то что делать? Неясно.
Здесь мало что сделаешь без важной идеи динамики по последнему
элементу. То есть давайте в dp[i] хранить размер НВП, которая заканчивается в
клетке i. Это добавляет какой-то определенности.
Тогда как свести ее к предыдущим? Перебрать предыдущий элемент! Он
должен быть левее и меньше. За O(n) переберем его и выберем максимум dp[i] в таких
элементах. Так сделаем для n элементов. Заметьте, что ответ лежит не в dp[n], а в
максимуме dp[i] по всему массиву, потому что последний элемент может быть любым.
НОП
Другая задача: есть две строки, надо найти их наибольшую общую
подпоследовательность (НОП). Например, у abacab и aabcba это строка abca (aaca
тоже подходит).
Здесь нужна уже двумерная динамика. Здесь задача решается без
премудростей - сводим задачу к предыдущей тривиально: просто dp[i][j] - это размер
НОП первых i символов первой строки и первых j символов второй строки.
Тогда заметим, что dp[0][k]=dp[k][0]=0.
И заметим, что dp[i][j] = dp[i-1][j-1], если s1[i] == s2[j], то есть если символы
равны, то они по-любому входят в НОП.
А если они не равны, то один из них по-любому не входит в НОП: dp[i][j] =
max(dp[i - 1][j], dp[i][j - 1]), если s1[i] != s2[j].
Вот и всё! Осталось восстановить ответ с помощью prev[i][j] старым способом и
получить перевернутый ответ. Удачи!