个人博客链接:https://blog.nuoyanli.com/2020/04/04/hdu3070/
假设大家已经快忘记了快速幂这个东西
一般对于 a b a^b ab我们只需要连续乘 b b b次 a a a就能得到答案了:
例如
2 3 2^3 23只需要连续乘 3 3 3次 2 2 2即可, 2 3 = 2 ∗ 2 ∗ 2 2^3=2*2*2 23=2∗2∗2
2 13 2^{13} 213只需要连续乘 13 13 13次 2 2 2即可, 2 13 = 2 ∗ 2 ∗ 2 ∗ 2 ∗ 2 ∗ 2 ∗ 2 ∗ 2 ∗ 2 ∗ 2 ∗ 2 ∗ 2 ∗ 2 2^{13}=2*2*2*2*2*2*2*2*2*2*2*2*2 213=2∗2∗2∗2∗2∗2∗2∗2∗2∗2∗2∗2∗2
如果你觉得还好,那么 2 100 , 2 1000 . . . 2^{100},2^{1000}... 2100,21000...呢?
回归到问题本身, a b a^b ab当 b b b很大的时候,是很费时间的,直接乘 b b b个 a a a往往会超时,所以就有了快速幂这个算法。
我们以 b = 13 b=13 b=13为例,把 b b b写成二进制:
b = 13 = 110 1 2 = 2 3 ∗ 1 + 2 2 ∗ 1 + 2 1 ∗ 0 + 2 0 ∗ 1 b=13=1101_2=2^3*1+2^2*1+2^1*0+2^0*1 b=13=11012=23∗1+22∗1+21∗0+20∗1
那么 a b = a 2 3 ∗ 1 + 2 2 ∗ 1 + 2 1 ∗ 0 + 2 0 ∗ 1 = a 2 3 ∗ a 2 1 ∗ a 2 0 a^b=a^{2^3*1+2^2*1+2^1*0+2^0*1}=a^{2^3}*a^{2^1}*a^{2^0} ab=a23∗1+22∗1+21∗0+20∗1=a23∗a21∗a20
通过观察我们不难发现,当 b b b的二进制第 i i i位上为 1 1 1,就乘上 a 2 i a^{2^i} a2i,这个值是可以由前面递乘出来的,所以这样一来大大减少了计算次数:
推广到 a b : a^b: ab:
①若 b b b为偶数, a b = ( a b / 2 ) 2 a^b=(a^{b/2})^2 ab=(ab/2)2
②若 b b b为奇数, a b = ( a ( b − 1 ) / 2 ) 2 ∗ a a^b=(a^{(b-1)/2})^2*a ab=(a(b−1)/2)2∗a
可以看到幂是指数级别下降的,因此最终复杂度是 O ( l o g n ) O(logn) O(logn)而不是朴素算法的 O ( n ) O(n) O(n)。
板子如下:
int qpow(int a, int b) {
int ans = 1;
while (b) {
if (b & 1) {
ans *= a;
}
a *= a;
b >>= 1;
}
return ans;
}
int qpow_mod(int a, int b, int c) {
a = a % c;
int ans = 1;
while (b) {
if (b & 1) {
ans = ans * a % c;
}
a = a * a % c;
b >>= 1;
}
return ans;
}
那么矩阵快速幂又是什么呢?
矩阵快速幂就是把整数变成矩阵用快速幂来算其次方而已, A n − 2 A^{n-2} An−2,其中 A = { 1 1 1 0 } A=\left\{ \begin{matrix}1& 1\\1&0\\ \end{matrix} \right\} A={1110},像这样的就是矩阵快速幂,那么为啥会有矩阵快速幂这个东西呢?
首先提一下矩阵的前置知识:
相信到这学期,大家已经学过了线性代数相关知识,我就不过多解释:
矩阵的一些名词定义:
矩阵的相关运算:
A + B A+B A+B,也就是对应 a i j 和 b i j a_{ij}和b_{ij} aij和bij相加重新构成的一个矩阵。
这里要注意的是,只有当两个矩阵是同型矩阵(也就是行数相等列数也相等)时,这两个矩阵才能进行加法运算。
一般,我们设 A = ( a i j ) A=(a_{ij}) A=(aij)是一个 m ∗ s m*s m∗s的矩阵, B = ( b i j ) B=(b_{ij}) B=(bij)是一个 s ∗ n s*n s∗n矩阵,那么规定矩阵 A A A与矩阵 B B B的乘积是一个 m ∗ n m*n m∗n的矩阵 C = ( c i j ) C=(c_{ij}) C=(cij),其中: c i j = a i 1 b 1 j + a i 2 b 2 j + ⋯ + a i k b i j = ∑ k = 1 s a i k b k j ( i = 1 , 2 , ⋯ , m ; j = 1 , 2 , ⋯ , n ) \begin{array}{c} c_{i j}=a_{i 1} b_{1 j}+a_{i 2} b_{2 j}+\cdots+a_{i k} b_{i j}=\sum_{k=1}^{s} a_{i k} b_{k j} \\ (i=1,2, \cdots, m ; j=1,2, \cdots, n) \end{array} cij=ai1b1j+ai2b2j+⋯+aikbij=∑k=1saikbkj(i=1,2,⋯,m;j=1,2,⋯,n)
并把此乘积记作: C = A B C=AB C=AB,也叫 A A A矩阵左乘 B B B矩阵,这里可以发现,矩阵的乘法是不满足交换律的,故乘法顺序对矩阵的影响。
也就是矩阵 A A A的对应行的每一个乘矩阵 B B B的对应列的每一个,然后相加构成的。
我们介绍完了矩阵的知识,那大家一定会问,矩阵快速幂有啥用呢?
我们不妨先回想忆一下斐波拉契数列, F ( n ) = { F ( 1 ) = 1 n = 1 F ( 2 ) = 1 n = 2 F ( n − 1 ) + F ( n − 2 ) n ≥ 3 F(n)=\left\{ \begin{array}{rcl} &F(1)=1&& {n= 1}\\&F(2)=1 && {n= 2}\\ &F(n-1)+F(n-2)&& {n\geq 3} \end{array} \right. F(n)=⎩⎨⎧F(1)=1F(2)=1F(n−1)+F(n−2)n=1n=2n≥3
如果我们要求某一项是要 O ( n ) O(n) O(n)的时间复杂度的,那么我们有没有一种可能降低时间复杂度呢?
答案是显然的,我们可以用上面说到的矩阵加速,我们不妨有这样的设想:
[ F ( n − 2 ) F ( n − 1 ) ] → [ F ( n − 1 ) F ( n ) ] → [ F ( n ) F ( n + 1 ) ] → [ F ( n + 1 ) F ( n + 2 ) ] … \left[\begin{array}{c} F(n-2) \\ F(n-1) \end{array}\right] \rightarrow\left[\begin{array}{c} F(n-1) \\ F(n) \end{array}\right] \rightarrow\left[\begin{array}{c} F(n) \\ F(n+1) \end{array}\right] \rightarrow\left[\begin{array}{c} F(n+1) \\ F(n+2) \end{array}\right] \dots [F(n−2)F(n−1)]→[F(n−1)F(n)]→[F(n)F(n+1)]→[F(n+1)F(n+2)]…
每个矩阵都可以独立的退出其后的所有矩阵,因为矩阵里面包含了所有有用的状态。
所以,我们只需要知道矩阵直接的箭头是什么操作,就可以利用第 1 1 1个矩阵推出之后的所有矩阵。
我们把第一步单独拿出来:
[ F ( n − 2 ) F ( n − 1 ) ] → [ F ( n − 1 ) F ( n ) ] \left[\begin{array}{c} F(n-2) \\ F(n-1) \end{array}\right] \rightarrow\left[\begin{array}{c} F(n-1) \\ F(n) \end{array}\right] [F(n−2)F(n−1)]→[F(n−1)F(n)]
把 F ( n ) F(n) F(n)拆成 F ( n − 1 ) + F ( n − 2 ) F(n-1)+F(n-2) F(n−1)+F(n−2):
[ F ( n − 2 ) F ( n − 1 ) ] → [ F ( n − 1 ) F ( n − 1 ) + F ( n − 2 ) ] \left[\begin{array}{c} F(n-2) \\ F(n-1) \end{array}\right] \rightarrow\left[\begin{array}{c} F(n-1) \\ F(n-1)+F(n-2) \end{array}\right] [F(n−2)F(n−1)]→[F(n−1)F(n−1)+F(n−2)]
然后稍微补充一下,构造完整的矩阵:
[ F ( n − 2 ) F ( n − 1 ) ] → [ F ( n − 1 ) × 1 + F ( n − 2 ) × 0 F ( n − 1 ) × 1 + F ( n − 2 ) × 1 ] \left[\begin{array}{l} F(n-2) \\ F(n-1) \end{array}\right] \rightarrow\left[\begin{array}{l} F(n-1) \times 1+F(n-2) \times 0 \\ F(n-1) \times 1+F(n-2) \times 1 \end{array}\right] [F(n−2)F(n−1)]→[F(n−1)×1+F(n−2)×0F(n−1)×1+F(n−2)×1]
第二个矩阵的形式我们是不是有点似曾相识…没错,这就是矩阵乘法:
[ 0 1 1 1 ] × [ F ( n − 2 ) F ( n − 1 ) ] = [ F ( n − 2 ) × 0 + F ( n − 1 ) × 1 F ( n − 2 ) × 1 + F ( n − 1 ) × 1 ] = [ F ( n − 1 ) F ( n ) ] \left[\begin{array}{ll} 0 & 1 \\ 1 & 1 \end{array}\right] \times\left[\begin{array}{l} F(n-2) \\ F(n-1) \end{array}\right]=\left[\begin{array}{l} F(n-2) \times 0+F(n-1) \times 1 \\ F(n-2) \times 1+F(n-1) \times 1 \end{array}\right]=\left[\begin{array}{c} F(n-1) \\ F(n) \end{array}\right] [0111]×[F(n−2)F(n−1)]=[F(n−2)×0+F(n−1)×1F(n−2)×1+F(n−1)×1]=[F(n−1)F(n)]
所有,上面那个递推过程中的箭头操作就是矩阵乘法操作,想到这里大家是不是释然了?
[ 0 1 1 1 ] × [ F ( n − 2 ) F ( n − 1 ) ] = [ F ( n − 1 ) F ( n ) ] [ 0 1 1 1 ] × [ F ( n − 1 ) F ( n ) ] = [ F ( n ) F ( n + 1 ) ] [ 0 1 1 1 ] × [ F ( n ) F ( n + 1 ) ] = [ F ( n + 1 ) F ( n + 2 ) ] \begin{aligned} &\left[\begin{array}{ll} 0 & 1 \\ 1 & 1 \end{array}\right] \times\left[\begin{array}{c} F(n-2) \\ F(n-1) \end{array}\right]=\left[\begin{array}{c} F(n-1) \\ F(n) \end{array}\right]\\ &\left[\begin{array}{ll} 0 & 1 \\ 1 & 1 \end{array}\right] \times\left[\begin{array}{c} F(n-1) \\ F(n) \end{array}\right]=\left[\begin{array}{c} F(n) \\ F(n+1) \end{array}\right]\\ &\left[\begin{array}{ll} 0 & 1 \\ 1 & 1 \end{array}\right] \times\left[\begin{array}{c} F(n) \\ F(n+1) \end{array}\right]=\left[\begin{array}{l} F(n+1) \\ F(n+2) \end{array}\right] \end{aligned} [0111]×[F(n−2)F(n−1)]=[F(n−1)F(n)][0111]×[F(n−1)F(n)]=[F(n)F(n+1)][0111]×[F(n)F(n+1)]=[F(n+1)F(n+2)]
…
所以: [ F ( n − 1 ) F ( n ) ] = [ 0 1 1 1 ] n − 2 × [ F ( 1 ) F ( 2 ) ] \left[\begin{array}{c} F(n-1) \\ F(n) \end{array}\right]=\left[\begin{array}{ll} 0 & 1 \\ 1 & 1 \end{array}\right]^{n-2} \times\left[\begin{array}{c} F(1) \\ F(2) \end{array}\right] [F(n−1)F(n)]=[0111]n−2×[F(1)F(2)]
[ 0 1 1 1 ] n − 2 \left[\begin{array}{ll} 0 & 1 \\ 1 & 1 \end{array}\right]^{n-2} [0111]n−2显然:矩阵快速幂!!!
只需要将上面的整数快速幂代码改成矩阵的即可了,详见代码:
以POJ3070为例:
[ F n + 1 F n F n F n − 1 ] = [ 1 1 1 0 ] n \left[\begin{array}{cc} F_{n+1} & F_{n} \\ F_{n} & F_{n-1} \end{array}\right]=\left[\begin{array}{ll} 1 & 1 \\ 1 & 0 \end{array}\right]^{n} [Fn+1FnFnFn−1]=[1110]n
#include
using namespace std;
typedef long long LL;
const int mod = 10000;
const int N = 110;
int n = 2;
struct mat {
int m[N][N];
} Mat;
mat operator*(mat a, mat b) {
mat ret;
LL x;
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
x = 0;
for (int k = 0; k < n; k++) {
x = (x + a.m[i][k] * b.m[k][j]) % mod;
}
ret.m[i][j] = x % mod;
}
}
return ret;
}
void init_Mat() {
for (int i = 0; i < N; i++) {
Mat.m[i][i] = 1;
}
}
mat pow_mat(mat a, LL n) {
mat ret = Mat;
while (n) {
if (n & 1) {
ret = ret * a;
}
a = a * a;
n >>= 1;
}
return ret;
}
int main() {
LL p;
init_Mat();
while (cin >> p && p != -1) {
mat a;
a.m[0][0] = 1;
a.m[0][1] = 1;
a.m[1][0] = 1;
a.m[1][1] = 0;
a = pow_mat(a, p);
cout << a.m[0][1] << endl;
}
return 0;
}
矩阵快速幂,简而言之就是利用矩阵加速数列递推,其中最难的也就是怎么推矩阵了,大家可以试一下下面几个例子哦: