快速幂
求数aaa的nnn次幂,可以采用二分法进行快速计算,即
an={an2⋅an2,n为偶数a⋅an2⋅an2,n为奇数 a^n=\left\{\begin{array}{ll} a^{\frac{n}{2}}\cdot a^{\frac{n}{2}}, & n为偶数\\ a\cdot a^{\frac{n}{2}}\cdot a^{\frac{n}{2}}, & n为奇数 \end{array}\right. an={a2n⋅a2n,a⋅a2n⋅a2n,n为偶数n为奇数
还可以这样考虑,将nnn表示为二进制,每一个进制位决定了是否取这一项的结果,且每一项可以由前一项平方迭代计算
an=a(b0b1b2b3⋯bk)2=ab020+b121+b222+b323+⋯bk2k=a1b0⋅a2b1⋅a4b2⋅a8b3⋯abk2k=ab0⋅(a⋅a)b1⋅(a2⋅a2)b2⋅(a4⋅a4)b3⋯(a2k−1⋅a2k−1)bk
\begin{aligned}a^n&=a^{(b_0b_1b_2b_3\cdots b_k)_2}\\
&=a^{b_02^0+b_12^1+b_22^2+b_32^3+\cdots b_k2^k}\\
&=a^{1b_0}\cdot a^{2b_1}\cdot a^{4b_2}\cdot a^{8b_3}\cdots a^{b_k2^k}\\
&=a^{b_0}\cdot (a\cdot a)^{b_1}\cdot (a^2\cdot a^2)^{b_2}\cdot (a^4\cdot a^4)^{b_3}\cdots (a^{2^{k-1}}\cdot a^{2^{k-1}})^{b_k}\end{aligned}
an=a(b0b1b2b3⋯bk)2=ab020+b121+b222+b323+⋯bk2k=a1b0⋅a2b1⋅a4b2⋅a8b3⋯abk2k=ab0⋅(a⋅a)b1⋅(a2⋅a2)b2⋅(a4⋅a4)b3⋯(a2k−1⋅a2k−1)bk
public int power(int a, int n){
int result = 1;
while (n > 0) {
if (n % 2 == 1) { // 还可以用 n & 1 == 1,表示取二进制最后一位
result *= a; // 二进制最后一位为1,表示取当前a
}
n /= 2; // 还可以用 n = n >> 1,表示二进制右移一位
a *= a; // 迭代计算下一位的基数
}
return result;
}
变形:如果对要求an%ka^n \% kan%k,对k取余操作可以直接放进每步迭代里
public int power(int a, int n, int k){
int result = 1;
while (n > 0) {
if (n % 2 == 1) {
result = (result * a) % k; // 对k取余
}
n /= 2;
a = (a * a) % k; // 对k取余
}
return result;
}
矩阵快速幂
和上述思路完全一样,只是全部对于矩阵乘法。斐波那契数列定义如下
f(n)={0,n=01,n=1f(n−2)+f(n−1),n≥2 f(n)=\left\{\begin{array}{ll} 0, & n=0\\ 1, & n=1\\ f(n-2)+f(n-1), & n\ge2 \end{array}\right. f(n)=⎩⎨⎧0,1,f(n−2)+f(n−1),n=0n=1n≥2
可以构造矩阵A=[1110]A=\left[\begin{array}{cc}1 \quad 1\\ 1 \quad 0\end{array}\right]A=[1110],则[f(n+1)f(n)f(n)f(n−1)]=An\left[\begin{array}{cc}f(n+1) \quad f(n)\\ f(n) \quad f(n-1)\end{array}\right]=A^n[f(n+1)f(n)f(n)f(n−1)]=An。其中AnA^nAn就可以使用矩阵快速幂计算。
public class Fibonacci {
static int[][] dot(int[][] A, int[][] B) {
int Arows = A.length;
int Acols = A[0].length;
int Brows = B.length;
int Bcols = B[0].length;
assert (Acols == Brows);
int tmp;
int[][] R = new int[Arows][Bcols];
for (int i = 0; i < Arows; i++) {
for (int j = 0; j < Bcols; j++) {
tmp = 0;
for (int k = 0; k < Acols; k++) {
tmp += A[i][k] * B[k][j];
}
R[i][j] = tmp;
}
}
return R;
}
static int fibonacci(int n) {
if (n == 0) return 0;
n -= 1;
int[][] result = new int[][]{{1, 0}, {0, 1}};
int[][] A = new int[][]{{1, 1}, {1, 0}};
while (n > 0) {
if (n % 2 == 1) {
result = dot(result, A);
}
n /= 2;
A = dot(A, A);
}
return result[0][0];
}
public static void main(String[] args) {
System.out.println(fibonacci(100000));
}
}