参考自tly大佬与mys大佬的博客:友链:
orz tly大佬
orz mys大佬(他的博客似乎在某个我不知道的地方QAQ…其实看到他的博客是找他要的pdf版本)
也有一部分来自讲课时的课件。
多项式的表示方法分为两种,一种是系数表达法,另一种是点值表达法。
就是常见的解析式了,一般的多项式表示成 f ( x ) = a 0 + a 1 x + a 2 x 2 + ⋯ a n x n = ∑ i = 0 N a i x i f(x) = a_0+a_1x+a_2x^2+\cdots a_nx^n=\sum_{i=0}^{N}a_ix^i f(x)=a0+a1x+a2x2+⋯anxn=∑i=0Naixi。
对于一个多项式 f ( x ) = ∑ i = 0 N a i x i f(x)=\sum_{i=0}^{N}a_ix^i f(x)=∑i=0Naixi,我们选取 n + 1 n+1 n+1个不同的 x i x_i xi带进去,得到了 n + 1 n+1 n+1个 y i y_i yi,这样用 n + 1 n+1 n+1个数对 ( x i , y i ) (x_i,y_i) (xi,yi)就可以表示一个多项式了。
不难发现一个点值表示唯一对应一个多项式,但一个多项式就能够对应多个点值表示了。
对于两个多项式 f ( x ) , g ( x ) f(x),g(x) f(x),g(x),我们把 { x 1 , x 2 , x 3 , … , x n } \{x_1,x_2,x_3,\ldots,x_n\} {x1,x2,x3,…,xn}代进去,得到了 f ( x ) , g ( x ) f(x),g(x) f(x),g(x)的点值表示: { ( x 1 , f ( x 1 ) ) , ( x 2 , f ( x 2 ) ) , ( x 3 , f ( x 3 ) ) , … , ( x n , f ( x n ) ) } \{(x_1,f(x_1)),(x_2,f(x_2)),(x_3,f(x_3)),\ldots,(x_n,f(x_n))\} {(x1,f(x1)),(x2,f(x2)),(x3,f(x3)),…,(xn,f(xn))}和 { ( x 1 , g ( x 1 ) ) , ( x 2 , g ( x 2 ) ) , ( x 3 , g ( x 3 ) ) , … , ( x n , g ( x n ) ) } \{(x_1,g(x_1)),(x_2,g(x_2)),(x_3,g(x_3)),\ldots,(x_n,g(x_n))\} {(x1,g(x1)),(x2,g(x2)),(x3,g(x3)),…,(xn,g(xn))}。
那么不难发现 f ( x ) ⋅ g ( x ) f(x)\cdot g(x) f(x)⋅g(x)的点值表示其实就等于 { ( x 1 , f ( x 1 ) g ( x 1 ) ) , ( x 2 , f ( x 2 ) g ( x 2 ) ) , ( x 3 , f ( x 3 ) g ( x 3 ) ) , … , ( x n , f ( x n ) g ( x n ) ) } \{(x_1,f(x_1)g(x_1)),(x_2,f(x_2)g(x_2)),(x_3,f(x_3)g(x_3)),\ldots,(x_n,f(x_n)g(x_n))\} {(x1,f(x1)g(x1)),(x2,f(x2)g(x2)),(x3,f(x3)g(x3)),…,(xn,f(xn)g(xn))}。
这样一来,我们只要取足够多的 x i x_i xi,就可以按照如下方式将两个多项式乘起来:
但这样做看上去对原来 O ( n 2 ) O(n^2) O(n2)的多项式乘法似乎没什么优化,但没错,接下来,FFT将把这个过程优化到 O ( n log n ) O(n\log n) O(nlogn)。
如何将算法优化到 O ( n log n ) O(n\log n) O(nlogn)?
这意味着我们需要选取一些特殊的值代进去。
这种值就叫单位复根。
单位复根的实质是复数。
复数是形如 a + b i a+bi a+bi的一种数,其中 i = − 1 i=\sqrt{-1} i=−1。称此时的 a a a为实部, b b b为虚部。
称此时它的长度为模长,与 x x x轴(实数轴)的夹角为幅角。
这样一来,复数的乘法就是模长相乘,幅角相加。
使得 ω n = 1 \omega^n = 1 ωn=1的复数 ω \omega ω为 n n n次单位复根。
不难发现 n n n次单位复根恰好有 n n n个,它们是 e 2 π i k n , k = 0 , 2 , … , n − 1 e^\frac{2\pi ik}{n},k=0,2,\ldots,n-1 en2πik,k=0,2,…,n−1。
复数的幂 e i θ = cos θ + sin θ i e^{i\theta}=\cos \theta+\sin\theta i eiθ=cosθ+sinθi。
称此时的 ω n = e 2 π i n \omega_n=e^\frac{2\pi i}{n} ωn=en2πi为主 n n n次单位复根,其他的 n n n次单位复根都是主单位复根的幂。
这样一来,单位复根就有了一些性质:
其实单位复根还有一些其他的数学性质:(这些性质都可以通过画图来证明)
若 d > 0 d>0 d>0,则有 ω d n d k = ω n k {\omega_{dn}}^{dk}={\omega_{n}}^k ωdndk=ωnk。
证明(推式子): ω d n d k = ( e 2 π i d n ) d k = ( e 2 π i n ) k = ω n k {\omega_{dn}}^{dk}=(e^\frac{2\pi i}{dn})^{dk}=(e^\frac{2\pi i}{n})^{k}={\omega_{n}}^k ωdndk=(edn2πi)dk=(en2πi)k=ωnk。
它有一个推论: ω 2 n n = ω 2 = − 1 {\omega_{2n}}^{n}=\omega_2=-1 ω2nn=ω2=−1。
ω 2 n k = − ω 2 n k + n {\omega_{2n}}^k=-{\omega_{2n}}^{k+n} ω2nk=−ω2nk+n。
证明就是将等式两边平方后发现它的值对应相等,而方向相反。
对于不能被 n n n整除的数 k k k有 ∑ j = 0 n − 1 ( ω n k ) j = 0 \sum_{j=0}^{n-1}{({\omega_n}^k)^j}=0 ∑j=0n−1(ωnk)j=0。
证明:不难发现这是一个等比数列,应用等比数列的求和公式可得: ∑ j = 0 n − 1 ( ω n k ) j = ( ω n k ) n − 1 ω n k − 1 = 1 k − 1 ω n k − 1 = 0 \sum_{j=0}^{n-1}{({\omega_n}^k)^j}=\frac{({\omega_n}^k)^n-1}{{\omega_n}^k-1}=\frac{1^k-1}{{\omega_n}^k-1}=0 ∑j=0n−1(ωnk)j=ωnk−1(ωnk)n−1=ωnk−11k−1=0。
我们选取 n n n次单位复根代入 n − 1 n-1 n−1次多项式。
为了加快运算,我们将多项式的奇次项和偶次项分开来算,最后将它们组合在一起。
记多项式 f ( x ) = a 0 + a 1 x + a 2 x 2 + ⋯ + a n − 1 x n − 1 f(x)=a_0+a_1x+a_2x^2+\cdots+a_{n-1}x^{n-1} f(x)=a0+a1x+a2x2+⋯+an−1xn−1。
将其分成两个多项式: f [ 0 ] ( x ) = a 0 + a 2 x + a 4 x 2 + ⋯ , f [ 1 ] ( x ) = a 1 + a 3 x + a 5 x 2 + ⋯ f^{[0]}(x)=a_0+a_2x+a_4x^2+\cdots,f^{[1]}(x)=a_1+a_3x+a_5x^2+\cdots f[0](x)=a0+a2x+a4x2+⋯,f[1](x)=a1+a3x+a5x2+⋯。
则不难得出 f ( x ) = f [ 0 ] ( x 2 ) + x f [ 1 ] ( x 2 ) f(x)=f^{[0]}(x^2)+xf^{[1]}(x^2) f(x)=f[0](x2)+xf[1](x2)。
我们取 n = 2 p n=2p n=2p,将 ω n k {\omega_n}^k ωnk和 ω n k + p {\omega_n}^{k+p} ωnk+p代入多项式得到:
f ( ω n k ) = f [ 0 ] ( ( ω n 2 k 2 ) 2 ) + ω n k f [ 1 ] ( ( ω n 2 k 2 ) 2 ) = f [ 0 ] ( ω p k ) + ω n k f [ 1 ] ( ω p k ) f({\omega_n}^k)=f^{[0]}(({\omega_\frac{n}{2}}^\frac{k}{2})^2)+{\omega_n}^kf^{[1]}(({\omega_\frac{n}{2}}^\frac{k}{2})^2)=f^{[0]}({\omega_p}^k)+{\omega_n}^kf^{[1]}({\omega_p}^k) f(ωnk)=f[0]((ω2n2k)2)+ωnkf[1]((ω2n2k)2)=f[0](ωpk)+ωnkf[1](ωpk)
f ( ω n k + p ) = f [ 0 ] ( ω p k 2 ) + ω n k + p f [ 1 ] ( ω p k + p ) = f [ 0 ] ( ω p k ) − ω n k f [ 1 ] ( ω p k ) f({\omega_n}^{k+p})=f^{[0]}({\omega_p}^\frac{k}{2})+{\omega_n}^{k+p}f^{[1]}({\omega_p}^{k+p})=f^{[0]}({\omega_p}^k)-{\omega_n}^kf^{[1]}({\omega_p}^k) f(ωnk+p)=f[0](ωp2k)+ωnk+pf[1](ωpk+p)=f[0](ωpk)−ωnkf[1](ωpk)
就像上面两个式子一样,只要我们知道了 f [ 0 ] ( ω p k ) f^{[0]}({\omega_p}^k) f[0](ωpk)和 f [ 1 ] ( ω p k ) f^{[1]}({\omega_p}^k) f[1](ωpk),就可以 O ( 1 ) O(1) O(1)的算出 f ( ω n k ) f({\omega_n}^k) f(ωnk)和 f ( ω n k + p ) f({\omega_n}^{k+p}) f(ωnk+p)。
因此,只要我们递归的求解 f [ 0 ] ( ω p k ) f^{[0]}({\omega_p}^k) f[0](ωpk)和 f [ 1 ] ( ω p k ) f^{[1]}({\omega_p}^k) f[1](ωpk),就可以在 O ( n ) O(n) O(n)的时间内算出 f ( x ) f(x) f(x)的点值表示。
时间复杂度为 O ( n log n ) O(n\log n) O(nlogn)。
递归版本的FFT虽然将计算多项式点值表示的时间复杂度降到了 O ( n log n ) O(n\log n) O(nlogn),但一个显然的问题是这个算法的常数太大了。
原来的序列:0,1,2,3,4,5,6,7。
变换后的序列:0,4,2,6,1,5,3,7。
将它们变成二进制:
原序列:000,001,010,011,101,110,111。
变换后的序列000,100,010,110,101,011,111。
这样我们就发现它们的值就是二进制位的反转。
我们将系数中该换位置的都换了,倒着做FFT就够了。
如何获得二进制位的反转呢?只要简洁就行了。具体还是参考代码吧。
但我们获得了点值表示,如果我们要将它变成系数表示呢?
观察FFT的实质,我们进行了一次矩阵乘法:
[ ( ω n 0 ) 0 ( ω n 0 ) 1 ( ω n 0 ) 2 ⋯ ( ω n 0 ) n − 1 ( ω n 1 ) 0 ( ω n 1 ) 1 ( ω n 1 ) 2 ⋯ ( ω n 1 ) n − 1 ⋮ ⋮ ⋮ ⋱ ⋮ ( ω n n − 1 ) 0 ( ω n n − 1 ) 2 ( ω n n − 1 ) 3 ⋯ ( ω n n − 1 ) n − 1 ] × [ a 0 a 1 ⋮ a n ] = [ f ( ω n 0 ) f ( ω n 1 ) ⋮ f ( ω n n − 1 ) ] \begin{bmatrix}({\omega_n}^0)^0&({\omega_n}^0)^1&({\omega_n}^0)^2&\cdots&({\omega_n}^0)^{n-1}\\({\omega_n}^1)^0&({\omega_n}^1)^1&({\omega_n}^1)^2&\cdots&({\omega_n}^1)^{n-1}\\\vdots&\vdots&\vdots&\ddots&\vdots\\({\omega_n}^{n-1})^0&({\omega_n}^{n-1})^2&({\omega_n}^{n-1})^3&\cdots&({\omega_n}^{n-1})^{n-1}\end{bmatrix} \times \begin{bmatrix}a_0\\a_1\\\vdots\\a_n\end{bmatrix}=\begin{bmatrix}f({\omega_n}^0)\\f({\omega_n}^1)\\\vdots\\f({\omega_n}^{n-1})\end{bmatrix} ⎣⎢⎢⎢⎡(ωn0)0(ωn1)0⋮(ωnn−1)0(ωn0)1(ωn1)1⋮(ωnn−1)2(ωn0)2(ωn1)2⋮(ωnn−1)3⋯⋯⋱⋯(ωn0)n−1(ωn1)n−1⋮(ωnn−1)n−1⎦⎥⎥⎥⎤×⎣⎢⎢⎢⎡a0a1⋮an⎦⎥⎥⎥⎤=⎣⎢⎢⎢⎡f(ωn0)f(ωn1)⋮f(ωnn−1)⎦⎥⎥⎥⎤
记上面的系数矩阵(第一个矩阵)为 V V V。
对于另一个矩阵 D = [ ( ω n − 0 ) 0 ( ω n − 0 ) 1 ( ω n − 0 ) 2 ⋯ ( ω n − 0 ) n − 1 ( ω n − 1 ) 0 ( ω n − 1 ) 1 ( ω n − 1 ) 2 ⋯ ( ω n − 1 ) n − 1 ⋮ ⋮ ⋮ ⋱ ⋮ ( ω n − n + 1 ) 0 ( ω n − n + 1 ) 2 ( ω n − n + 1 ) 3 ⋯ ( ω n − n + 1 ) n − 1 ] D=\begin{bmatrix}({\omega_n}^{-0})^0&({\omega_n}^{-0})^1&({\omega_n}^{-0})^2&\cdots&({\omega_n}^{-0})^{n-1}\\({\omega_n}^{-1})^0&({\omega_n}^{-1})^1&({\omega_n}^{-1})^2&\cdots&({\omega_n}^{-1})^{n-1}\\\vdots&\vdots&\vdots&\ddots&\vdots\\({\omega_n}^{-n+1})^0&({\omega_n}^{-n+1})^2&({\omega_n}^{-n+1})^3&\cdots&({\omega_n}^{-n+1})^{n-1}\end{bmatrix} D=⎣⎢⎢⎢⎡(ωn−0)0(ωn−1)0⋮(ωn−n+1)0(ωn−0)1(ωn−1)1⋮(ωn−n+1)2(ωn−0)2(ωn−1)2⋮(ωn−n+1)3⋯⋯⋱⋯(ωn−0)n−1(ωn−1)n−1⋮(ωn−n+1)n−1⎦⎥⎥⎥⎤
考虑 D × V D\times V D×V:
( D × V ) i , j = ∑ k = 1 n − 1 D i , k ⋅ V i , k = ∑ i = 1 n − 1 ω n − i k ⋅ ω n j k = ∑ k = 1 n − 1 ω n ( j − i ) k \begin{aligned}(D\times V)_{i,j}&=\sum_{k=1}^{n-1}D_{i,k}\cdot V_{i,k}\\&=\sum_{i=1}^{n-1}{\omega_n}^{-ik}\cdot{\omega_n}^{jk}\\&=\sum_{k=1}^{n-1}{\omega_n}^{(j-i)k}\end{aligned} (D×V)i,j=k=1∑n−1Di,k⋅Vi,k=i=1∑n−1ωn−ik⋅ωnjk=k=1∑n−1ωn(j−i)k
当 i = j i=j i=j时, ( D × V ) i , j = ∑ k = 1 n − 1 ω n ( j − i ) k = ∑ k = 1 n − 1 ω n 0 = n \begin{aligned}(D\times V)_{i,j}&=\sum_{k=1}^{n-1}{\omega_n}^{(j-i)k}\\&=\sum_{k=1}^{n-1}{\omega_n}^{0}\\&=n\end{aligned} (D×V)i,j=k=1∑n−1ωn(j−i)k=k=1∑n−1ωn0=n
当 i ≠ j i\ne j i=j时, ( D × V ) i , j = ∑ k = 1 n − 1 ω n ( j − i ) k = 1 + ( ω n j − i ) 1 + ( ω n j − i ) 2 + ⋯ + ( ω n j − i ) n − 1 = 1 − ( ω n j − i ) n 1 − ω n j − i = 0 \begin{aligned}(D\times V)_{i,j}&=\sum_{k=1}^{n-1}{\omega_n}^{(j-i)k}\\&=1+({\omega_n}^{j-i})^1+({\omega_n}^{j-i})^2+\cdots+({\omega_n}^{j-i})^{n-1}\\&=\frac{1-({\omega_n}^{j-i})^n}{1-{\omega_n}^{j-i}}\\&=0\end{aligned} (D×V)i,j=k=1∑n−1ωn(j−i)k=1+(ωnj−i)1+(ωnj−i)2+⋯+(ωnj−i)n−1=1−ωnj−i1−(ωnj−i)n=0
于是 D × V × [ a 0 a 1 ⋮ a n ] = [ n 0 0 ⋯ 0 0 n 0 ⋯ 0 ⋮ ⋮ ⋮ ⋱ ⋮ 0 0 0 ⋯ n ] × [ a 0 a 1 ⋮ a n ] = [ n a 0 n a 1 ⋮ n a n ] D\times V\times\begin{bmatrix}a_0\\a_1\\\vdots\\a_n\end{bmatrix}=\begin{bmatrix}n&0&0&\cdots&0\\0&n&0&\cdots&0\\\vdots&\vdots&\vdots&\ddots&\vdots\\0&0&0&\cdots&n\end{bmatrix} \times \begin{bmatrix}a_0\\a_1\\\vdots\\a_n\end{bmatrix}=\begin{bmatrix}na_0\\na_1\\\vdots\\na_n\end{bmatrix} D×V×⎣⎢⎢⎢⎡a0a1⋮an⎦⎥⎥⎥⎤=⎣⎢⎢⎢⎡n0⋮00n⋮000⋮0⋯⋯⋱⋯00⋮n⎦⎥⎥⎥⎤×⎣⎢⎢⎢⎡a0a1⋮an⎦⎥⎥⎥⎤=⎣⎢⎢⎢⎡na0na1⋮nan⎦⎥⎥⎥⎤
所以再给我们得到的点值表示乘上这个矩阵 D D D,每个值除以 n n n后就得到了多项式的系数表示。
代码中定义了复数类,方便运算。
好像STL里面也有一个复数类,但似乎跑得有点慢。
顺便提一句,由于FFT要求所代入的多项式必须是2的整数次幂,所以我们将多项式的项数扩充到了2的整数次幂。
const double PI = 3.1415926535;
struct Complex {
double a, b;
Complex(double x = 0, double y = 0) {a = x, b = y;}
Complex operator + (const Complex &rhs) const {return Complex(a + rhs.a, b + rhs.b);}
Complex operator - (const Complex &rhs) const {return Complex(a - rhs.a, b - rhs.b);}
Complex operator * (const Complex &rhs) const {return Complex(a * rhs.a - b * rhs.b, a * rhs.b + b * rhs.a);}
};
int rev[Maxsize + 5];
void GetReverse(int len, int l) {
for(int i = 0; i < len; i++)
rev[i] = 0;
for(int i = 0; i < len; i++)
rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (l - 1));
}
inline Complex EXP(double theta) {
return Complex(cos(theta), sin(theta));
}
void FFT(vector<Complex> &a, int dir) {
int len = a.size();
for(int i = 0; i < len; i++)
if(i < rev[i]) swap(a[i], a[rev[i]]);
for(int stp = 1; stp < len; stp <<= 1)
for(int k = 0; k < stp; k++) {
Complex wk = EXP(dir * k * PI / stp);
for(int even = k; even < len; even += stp << 1) {
int odd = even + stp;
Complex tmp = a[odd] * wk;
a[odd] = a[even] - tmp;
a[even] = a[even] + tmp;
}
}
if(dir == -1)
for(int i = 0; i < len; i++)
a[i].a /= len;
}
不难发现FFT中大量用到了实数的运算和三角函数,这会导致精度、常数等问题。
我们是否可以在模意义下找到一种数,使得这些都满足单位复根的性质?
想一想我们用到的单位复根的性质:
换句话说,我们要找出一种数,要求在模意义下满足上述条件。
对于一个素数 P P P,若有一个数 G G G使得 G 1 , G 2 , G 3 , … , G p − 2 ( m o d P ) G^1,G^2,G^3,\ldots,G^{p-2}(\mod P) G1,G2,G3,…,Gp−2(modP)互不相同,则 G G G是 P P P的一个原根。
再定义 g n k = ( G P − 1 n ) k {g_n}^k=(G^\frac{P-1}{n})^k gnk=(GnP−1)k,检验一下是否满足上述性质。
然后就是将所有涉及到复数运算的部分全部替换成原根即可。
你以为将单位复根换成原根就完了?
事实上,我们知道FFT需要将多项式的项数扩充到二的整数次幂,而NTT也一样。
换句话说,NTT的模数的 P P P应该满足 P = k × 2 m + 1 P=k\times 2^m+1 P=k×2m+1,例如常用的模数 998244353 = 7 × 17 × 2 23 + 1 998244353=7\times17\times2^{23}+1 998244353=7×17×223+1。
如果记不住原根可以按文章末尾的方法求出。
const int Mod = 998244353;
const int G = 3;
int QuickPow(int a, int k) {
int ret = 1;
while(k) {
if(k & 1) ret = 1LL * ret * a % Mod;
a = 1LL * a * a % Mod;
k >>= 1;
}
return ret;
}
int rev[Maxsize + 5];
inline void GetReverse(int len, int lg) {
for(int i = 0; i < len; i++) rev[i] = 0;
for(int i = 0; i < len; i++)
rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (lg - 1));
}
void NTT(vector<int> &a, int dir) {
int len = a.size();
for(int i = 0; i < len; i++)
if(i < rev[i]) swap(a[i], a[rev[i]]);
int g = (dir == 1 ? G : QuickPow(G, Mod - 2));
for(int stp = 1; stp < len; stp <<= 1) {
int wn = QuickPow(g, (Mod - 1) / (stp << 1));
int w = 1;
for(int k = 0; k < stp; k++) {
for(int even = k; even < len; even += stp << 1) {
int odd = even + stp;
int tmp = 1LL * w * a[odd] % Mod;
a[odd] = (a[even] - tmp + Mod) % Mod;
a[even] = (ll)(a[even] + tmp) % Mod;
}
w = 1LL * w * wn % Mod;
}
}
if(dir == -1) {
int inv = QuickPow(len, Mod - 2);
for(int i = 0; i < len; i++)
a[i] = 1LL * a[i] * inv % Mod;
}
}
如何求一个奇素数 p p p的原根?
常用的方法是先将 p − 1 p-1 p−1唯一分解成 p 1 k 1 p 2 k 2 ⋯ p m k m p_1^{k_1}p_2^{k_2}\cdots p_m^{k_m} p1k1p2k2⋯pmkm。
然后再暴力枚举原根,对于一个数 g g g,只要满足对于 p − 1 p-1 p−1的任意一个质因数都有 g p − 1 p i ≡ 1 ( m o d P ) g^\frac{p-1}{p_i}\equiv1(\mod P) gpip−1≡1(modP)成立,那么这个数就是原根。
一般的数的原根都不大,直接枚举即可。
参考自:https://blog.csdn.net/hnust_xx/article/details/76572828
对于NTT的模数 P = k × 2 m + 1 P=k\times 2^m+1 P=k×2m+1列出常用的部分和它们的原根。
P = k × 2 m + 1 P=k\times 2^m+1 P=k×2m+1 | k k k | m m m | g g g |
---|---|---|---|
3 | 1 | 1 | 2 |
5 | 1 | 2 | 2 |
17 | 1 | 4 | 3 |
97 | 3 | 5 | 5 |
193 | 3 | 6 | 5 |
257 | 1 | 8 | 3 |
7681 | 15 | 9 | 17 |
12289 | 3 | 12 | 11 |
40961 | 5 | 13 | 3 |
65537 | 1 | 16 | 3 |
786433 | 3 | 18 | 10 |
5767169 | 11 | 19 | 3 |
7340033 | 7 | 20 | 3 |
23068673 | 11 | 21 | 3 |
104857601 | 25 | 22 | 3 |
167772161 | 5 | 25 | 3 |
469762049 | 7 | 26 | 3 |
998244353 | 119 | 23 | 3 |
1004535809 | 479 | 21 | 3 |
2013265921 | 15 | 27 | 31 |
2281701377 | 17 | 27 | 3 |
3221225473 | 3 | 30 | 5 |
7516927681 | 35 | 31 | 3 |
77309411329 | 9 | 33 | 7 |
206158430209 | 3 | 36 | 22 |
2061584302081 | 15 | 37 | 7 |
2748779069441 | 5 | 39 | 3 |
6597069766657 | 3 | 41 | 5 |
39582418599937 | 9 | 42 | 5 |
79164837199873 | 9 | 43 | 5 |
263882790666241 | 15 | 4 | 7 |
1231453023109121 | 35 | 45 | 3 |
1337006139375617 | 19 | 46 | 3 |
3799912185593857 | 27 | 47 | 5 |
4222124650659841 | 15 | 48 | 19 |
7881299347898396 | 7 | 50 | 6 |
31525197391593473 | 7 | 52 | 3 |
180143985094819841 | 5 | 55 | 6 |
1945555039024054237 | 27 | 56 | 5 |
4179340454199820289 | 29 | 57 | 3 |