声速幂

· · 算法·理论

省流:固定底数;对于 k≤wO(w2^k/k) 预处理,O(w/k) 询问。

前言

前置知识:快速幂 光速幂也可以在里面找到。

符号表示:在本文中,W 为指数的数域,w = \log_2 Wa 为底数。

对于结果有模数的幂,注意到 a^b \equiv a^{(b \bmod \varphi(p)) + \varphi(p)} \pmod p,因此有 W = 2\varphi(p)。对于素模数有 W = p - 1

由于洛谷上没有讲光速幂的文章,这里简要讲一下。

:::info[光速幂] 对于很小的 W,我们可以 O(W) 地预处理出幂的值。

对于较大的 W 则不行。

s \le W,我们知道

a^b = a^{\lfloor b/s\rfloor}\cdot a^{b \bmod s}

因此我们预处理 f_i = a^i\;(0 \le i < s)g_i = a^{is} \; (0 \le i \le (W-1)/s),则 a^b = g_{b/s}\cdot f_{b\bmod s}

注意到取 s = O(\sqrt{W}) 时,复杂度是最佳的。 :::

原理

同样解决固定底数的幂,对于 k\le w,可以在 O(w2^k/k) 的时间内预处理一个底数,在 O(w/k) 的时间内回答一个询问。

我们考虑处理一个 s = 2^k 进制的快速幂。很明显,这个 s 进制的数一共有 w/k 位。

比如 w=8,k=4

a^{10010110_2} = a^{1001\textcolor{lightgray}{0000}_2} \cdot a^{\textcolor{lightgray}{0000}0110_2} 对于这个例子,我们要预处理出 (〇)$a^{\textcolor{lightgray}{0000}0000_2}\sim a^{\textcolor{lightgray}{0000}1111_2}$, (一)$a^{0000\textcolor{lightgray}{0000}_2}\sim a^{1111\textcolor{lightgray}{0000}_2}$。 考虑缩小 $k$,比如 $w = 8,k = 2$: $$ a^{10010110_2} = a^{\textcolor{lightgray}{00}\textcolor{lightgray}{0000}10_2} \cdot a^{\textcolor{lightgray}{0000}01\textcolor{lightgray}{00}_2} \cdot a^{\textcolor{lightgray}{00}01\textcolor{lightgray}{0000}_2} \cdot a^{10\textcolor{lightgray}{00}\textcolor{lightgray}{0000}_2} . $$ 这时候,我们就需要预处理出 - (〇)$a^{\textcolor{lightgray}{00}\textcolor{lightgray}{0000}00_2} \sim a^{\textcolor{lightgray}{00}\textcolor{lightgray}{0000}11_2}$, - (一)$a^{\textcolor{lightgray}{0000}00\textcolor{lightgray}{00}_2} \sim a^{\textcolor{lightgray}{0000}11\textcolor{lightgray}{00}_2}$, - (二)$a^{\textcolor{lightgray}{00}00\textcolor{lightgray}{0000}_2} \sim a^{\textcolor{lightgray}{00}11\textcolor{lightgray}{0000}_2}$, - (三)$a^{00\textcolor{lightgray}{00}\textcolor{lightgray}{0000}_2} \sim a^{11\textcolor{lightgray}{00}\textcolor{lightgray}{0000}_2}$。 考虑如何预处理。我们令 $f(i,j) = a^{is^j}\;(0\le i<s,0\le j<w/k)$。可以验证,这就是以上预处理的内容。换言之,$f(i,j)=a^{i2^{jk}}$。 - 首先枚举计算 $a^{2^i}, 0\le i<w$。 - 对于 $i=0$,明显有 $f(i,j)=1$。 - $f(i+1,j)=a^{(i+1)s^j}=a^{is^j+s^j}=a^{is^j}\cdot a^{s^j}=f(i,j)\cdot a^{s^j}$。 形式化地, $$ \begin{aligned} f(i,j)&=1\quad&(i=0)\\ f(i,j)&=f(i-1,j)\cdot a^{s^j}\quad&(i>0) \end{aligned} $$ 显然是 $O(ws/k)$ 的。 对于询问,只需要将这个 $s$ 进制的指数 $b$ 的每一位预处理出的值相乘。 形式化地, $$a^b = \prod_{i=0}^{w/k-1}f\left((a/s^i)\bmod s,i\right)$$ 很明显是 $O(w/k)$ 的。 ## 例题 :::info[例题]{open} 令 $W = 2^{64}$。 给定 $a,b,n$,求 $\displaystyle \bigoplus_{i=1}^n a^{b^i\bmod W}\bmod W$。 $0 \le a,b < W, 1 \le n \le 10^8$。 ::: 令 $\phi = \varphi(W) = 2^{63}$,则当 $b^i\ge \phi$ 时,$a^{b^i}\equiv a^{(b^i \bmod \phi)+\phi}\pmod {W}$,否则不能降幂。因为此时 $b^i<2\phi$,故没有降幂。 此题中,$w = 64$,我们取 $k = 16$。 :::info[参考代码] ```cpp // C++14(GCC 9) O2 洛谷 IDE #include <bits/stdc++.h> using namespace std; typedef size_t Z; constexpr Z N = 5 + 1e9, M = 65535, S = 65536; Z a, b, cur = 1, n, ax[64], ans; Z f0[S], f1[S], f2[S], f3[S]; int main() { cin >> a >> b >> n; // 预处理 // ax[i] = pow(a, pow(2, i)); ax[0] = a; for (Z i = 1; i < 64; ++i) ax[i] = ax[i - 1] * ax[i - 1]; Z x0 = ax[0], x1 = ax[16], x2 = ax[32], x3 = ax[48]; // f##j[i] = pow(a, i * pow(65536, j)); f0[0] = f1[0] = f2[0] = f3[0] = 1; for (Z i = 1; i < 65536; ++i) { f0[i] = f0[i - 1] * x0; f1[i] = f1[i - 1] * x1; f2[i] = f2[i - 1] * x2; f3[i] = f3[i - 1] * x3; } for (Z i = 1; i <= n; ++i) { cur *= b; ans ^= f0[cur & M] * f1[cur >> 16 & M] * f2[cur >> 32 & M] * f3[cur >> 48 & M]; // 询问 } cout << ans; return 0; } ``` ::: 运行时间约 $500$ 毫秒,无氧 $900$ 毫秒,空间 $2560$ kb,正确性已经过对比验证。 一般快速幂应该过不了。