Быстрое возведение в степень

Пусть у нас есть какая-то ассоциативная бинарная операция \circ в алгебре A\AAA. Мы хотим научиться быстро считать степень элемента aa носителя алгебры A\AAA по этой операции, то есть значение

an=aaaan разa^n = \underbrace{a \circ a \circ \dotsb \circ a \circ a}_{n ~\text{раз}}

В псевдокоде я заменю операцию \circ на умножение, но это просто для удобства написания. В формулах я буду использовать оригинальное обозначение, дабы подчеркнуть универсальность.

Можно применить свойство ассоциативности для сведения задачи возведения в степень nn к вдвое меньшей задаче, используя простое свойство

an={an/2ann чётноеaan1n нечётноеa^n = \cases{a^{n/2} \circ a^n & n~\text{чётное} \\ a \circ a^{n-1} & n~\text{нечётное}}

Из этого простого свойства можно сразу вывести простой рекурсивный алгоритм

function fast_pow(a, int n): if n % 2 == 0: return fast_pow(a, n/2) ** 2 else: return a * fast_pow(a, n-1)

У рекурсии есть оверхед, от которого хотелось бы избавиться.

Давайте посмотрим на двоичное представление nn

n=(nknk1n2n1n0)2=nk2k+nk12k1++n24+n12+n0n = (n_k \, n_{k-1} \, \dotsm \, n_2 \, n_1 \, n_0)_2 = n_k \cdot 2^k + n_{k-1} \cdot 2^{k-1} + \dotsb + n_2 \cdot 4 + n_1 \cdot 2 + n_0

Тогда нашу степень можно переписать как

n=(((((nk2+nk1)2+nk2)2+)2+n2)2+n1)2+n0n = ((( \dotsm ((n_k \cdot 2 + n_{k-1}) \cdot 2 + n_{k-2}) \cdot 2 + \dotsm) \cdot 2 + n_2) \cdot 2 + n_1) \cdot 2 + n_0

Ну и здесь сразу видно, как производить вычисления

function fast_pow(a, int n): int result = 1 while n > 0: if n % 2 == 1: result *= a a *= a n /= 2; return result

Важное замечание: 11 в этом алгоритме — нейтральный элемент по операции \circ.

Оценим количество умножений, требуемых алгоритмом (не важно каким, они одинаковые). Пусть для возведения числа aa в степень nn требуется M(n)M(n) умножений. Тогда, исходя из нашего алгоритма, справедлива формула

M(n)=M(n2)+1+(nmod2)M(n) = M\left( \left\lfloor \frac{n}{2} \right\rfloor \right) + 1 + (n \bmod 2)

Решение этого уравнения

M(n)=log2n+ν(n)1M(n) = \lfloor \log_2 n \rfloor + \nu(n) - 1

где ν(n)\nu(n) — количество единиц в числе nn

Предподсчёт

Пусть у нас есть фиксированный объект aa. К нам поступает qq запросов возведения aa в степень mim_i (ii - номер запроса).

Только что мы научились решать эту задачу за i=1q(log2mi+ν(mi))\sum\limits_{i=1}^q \bigl( \lfloor \log_2 m_i \rfloor + \nu(m_i) \bigr) умножений, что в итоге выливается в временную сложность O(qlogmaxm)time(одно умножение)O(q \cdot \log \max\limits m) \cdot \time(\text{одно умножение})

Аддитивные цепочки и оптимальный алгоритм