Когда мы говорили о линейных функциях и матрицах, все примеры были про геометрию. Так просто проще думать: представлять в уме повороты и растяжения векторов на плоскости легче, чем рассуждать о свойствах абстрактных n-мерных пространств. Однако мы программисты, и нас в основном интересуют задачи вне геометрии.
Большинство применений в контексте олимпиад опираются на ассоциативность матричного умножения:
Если задачу можно свести к возведению матрицы в какую-то большую степень — то есть к домножению единичной матрицы раз на матрицу — то можно просто воспользоваться свойством ассоциативности и посчитать результат бинарным возведением в степень:Такой подход имеет множество применений в динамическом программировании, комбинаторике, теории вероятностей и математике в целом.
#Линейные рекурренты
Задача. Дано число . Требуется вычислить -ное число Фибоначчи:
Заметим, что вычисление очередного числа Фибоначчи выражается как линейная функция от двух предыдущих чисел Фибоначчи — а именно, как их сумма.
Это означает, что мы можем построить матрицу , которая будет соответствовать этому преобразованию: как по двум соседним числам Фибоначчи вычислить следующее число, то есть перейти к паре .
Обозначим за эту матрицу перехода. Чтобы посчитать -ное число Фибоначчи, нужно применить раз эту матрицу к вектору .
Так как размер матрицы константный, её умножение саму на себя будет работать за . Значит, можно посчитать бинарным возведением в степень за операций, домножить на вектор и взять первый элемент результата — он будет равен в точности , что мы и искали.
Примечание. Домножать на вектор явно даже не обязательно — если нам нужен только первый элемент, можно посмотреть на выражение для итогового результата и понять, что достаточно просто взять верхний правый элемент итоговой матрицы.
#Общий случай
В общем случае, когда мы учитываем не , а последних элементов, причём с разными весами, линейная рекуррента имеет такую матрицу перехода:
При домножении на вектор для последнего элемента получается в точности формула из определения, а все предыдущих просто перекопируются.
Отметим, что рекуррентные последовательности задаются не только коэффициентами , но и начальными значениями .
Упражнение. Для линейной рекурренты порядка известны её коэффициенты и пар вида . Восстановите первые элементов последовательности.
#Геометрическая прогрессия
В статье про бинарное возведение в степень мы обсуждали модификацию алгоритма для нахождения суммы геометрической прогрессии:
Можно подойти к этой задаче с другой стороны. Заметим, что сумма первых степеней следующим образом выражается через сумму первых степеней: Это почти линейная зависимость от предыдущего — только ещё добавляется неудобная константа. Можно применить следующий трюк: представить, что мы также зависим ещё и от -го элемента, но положить его постоянно равным единице:Тогда эту матрицу можно аналогично возвести в степень и вернуть её правое верхнее поле.
#Динамическое программирование
Переходы в некоторых динамиках иногда тоже можно выразить в терминах матричного умножения.
Задача. Дана ориентированный граф размера . Требуется найти количество путей из в через ровно переходов.
Если бы ограничение на было сильно меньше, мы бы ввели динамику , равную количеству способов дойти до -той вершины через ровно переходов. Пересчёт — перебор предпоследней вершины в пути:
Перепишем эту динамику в терминах матрицы смежности, в ячейке которой содержится число ребер между и (ровно поэтому она называется матрицей). Выясняется, что вектор зависит от линейно: его -тый элемент получается скалярным умножением вектора и -того столбца матрицы смежности . Значит, имеет место следующее равенство:Получается, можно раскрыть как и применить бинарное возведение в степень.
По этой формуле нам потребуется раз перемножить две матрицы размера , что суммарно будет работать за .
const int n;
matrix binpow(matrix a, int p) {
// создадим единичную матрицу
matrix b(n, vector<int>(n, 0));
for (int i = 0; i < n; i++)
b[i][i] = 1;
while (p > 0) {
if (p & 1)
b = matmul(b, a);
a = matmul(a, a);
p >>= 1;
}
return b;
}
В получившейся матрице в ячейке будет находиться количество способов дойти из -той вершины в -тую, используя ровно переходов. Ответом нужно просто вывести .
#Модификации задачи
С небольшими изменениями этим методом можно решать много похожих задач:
- Практически не меняя сам алгоритм, можно решить задачу «с какой вероятностью мы попадём из вершины в вершину », если вместо матрицы смежности даны вероятности, с которыми мы переходим из вершины в вершину в марковской цепи.
- Если нам не нужно количество способов, а только сам факт, можно ли дойти за ровно переходов, то можно обернуть матрицу в битсеты и сильно ускорить решение.
- Если нас спрашивают «за не более, чем переходов», то вместо -той степени матрицы мы можем вышеописанным методом посчитать сумму геометрической прогрессии. Альтернативно, можно для каждой вершины добавить вторую, в которую будет вести ребро из изначальной, а единственное исходящее ребро — это петля в саму себя.
Эту технику можно применить и к другим динамикам, где нужно посчитать количество способов что-то сделать — иногда очень неочевидными способами.
Например, можно решить такую задачу: найти количество строк длины , не содержащих данные маленькие запрещённые подстроки. Для этого нужно построить граф «легальных» переходов в Ахо-Корасике, возвести его матрицу смежности в -тую степень и просуммировать в нём первую строчку.
В некоторых изощрённых случаях в матричном умножении вместо умножения и сложения нужно использовать другие операции, которые ведут себя как умножение и сложение. Пример задачи: «найти путь от до с минимальным весом ребра, использующий ровно переходов»; здесь нужно возводить в -ую степень матрицу весов графа, и вместо и сложения, и умножения использовать минимум из двух весов.
Также в контексте нахождения кратчайших путей известен «distance product»:
Возводя матрицу весов в -тую степень мы получаем, соответственно, минимальное расстояние от до , используя не более переходов. В частности, при мы за операций получим матрицу кратчайших расстояний между всеми парами вершины, хотя для этого есть и более прямые методы.