从快速幂到矩阵快速幂入门

21 篇文章 0 订阅
2 篇文章 0 订阅

个人博客链接: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=222

  • 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=2222222222222

  • 如果你觉得还好,那么 2 100 , 2 1000 . . . 2^{100},2^{1000}... 210021000...呢?

回归到问题本身, 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=231+221+210+201

那么 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=a231+221+210+201=a23a21a20

通过观察我们不难发现,当 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(b1)/2)2a

可以看到幂是指数级别下降的,因此最终复杂度是 O ( l o g n ) O(logn) O(logn)而不是朴素算法的 O ( n ) O(n) O(n)

板子如下:

  • a b a^b ab
int qpow(int a, int b) {
  int ans = 1;
  while (b) {
    if (b & 1) {
      ans *= a;
    }
    a *= a;
    b >>= 1;
  }
  return ans;
}
  • a b % c a^b\%c ab%c
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} An2,其中 A = { 1 1 1 0 } A=\left\{ \begin{matrix}1& 1\\1&0\\ \end{matrix} \right\} A={1110},像这样的就是矩阵快速幂,那么为啥会有矩阵快速幂这个东西呢?

首先提一下矩阵的前置知识:

相信到这学期,大家已经学过了线性代数相关知识,我就不过多解释:

矩阵的一些名词定义

  • n n n阶矩阵: { 1 1 1 0 } \left\{ \begin{matrix}1& 1\\1&0\\ \end{matrix} \right\} {1110}这就是一个 2 ∗ 2 2*2 22的矩阵,也叫 2 2 2阶矩阵
  • 行向量:只有一行的矩阵,也叫行矩阵, ( a 1 , a 2 , . . . , a n ) (a_1,a_2,...,a_n) (a1,a2,...,an)这就是一个行向量
  • 列向量:只有一列的矩阵,也叫列矩阵, { a 1 a 2 a 3 … a n } \left\{ \begin{matrix} a_1\\ a_2 \\ a_3 \\ \ldots\\ a_n \end{matrix} \right\} a1a2a3an,这就是一个列向量。

矩阵的相关运算:

  • 矩阵加法:设有两个 m ∗ n m*n mn的矩阵 A = ( a i j ) , B = ( b i j ) A=(a_{ij}),B=(b_{ij}) A=(aij)B=(bij),那么矩阵 A A A B B B的和记做 A + B A+B A+B,规定为: A + B = ( a 11 + b 11 a 12 + b 12 ⋯ a 1 n + b 1 n a 21 + b 21 a 22 + b 22 ⋯ a 2 n + b 2 n ⋮ ⋮ ⋮ a m 1 + b m 1 a m 2 + b m 2 ⋯ a m n + b m n ) \boldsymbol{A}+\boldsymbol{B}=\left(\begin{array}{cccc} a_{11}+b_{11} & a_{12}+b_{12} & \cdots & a_{1 n}+b_{1 n} \\ a_{21}+b_{21} & a_{22}+b_{22} & \cdots & a_{2 n}+b_{2 n} \\ \vdots & \vdots & & \vdots \\ a_{m 1}+b_{m 1} & a_{m 2}+b_{m 2} & \cdots & a_{m n}+b_{m n} \end{array}\right) A+B=a11+b11a21+b21am1+bm1a12+b12a22+b22am2+bm2a1n+b1na2n+b2namn+bmn

A + B A+B A+B,也就是对应 a i j 和 b i j a_{ij}和b_{ij} aijbij相加重新构成的一个矩阵。

这里要注意的是,只有当两个矩阵是同型矩阵(也就是行数相等列数也相等)时,这两个矩阵才能进行加法运算。

  • 矩阵乘法:设有两个 2 ∗ 3 2*3 23的矩阵, A = ( a 11 a 12 a 13 a 21 a 22 a 23 ) B = ( b 11 b 12 b 21 b 22 b 31 b 32 ) 那 么 A ∗ B = ( a 11 b 11 + a 12 b 21 + a 13 b 31 a 11 b 12 + a 12 b 22 + a 13 b 32 a 21 b 11 + a 22 b 21 + a 23 b 31 a 21 b 12 + a 22 b 22 + a 23 b 32 ) \begin{array}{l} \quad A= \left(\begin{array}{lll} a_{11} & a_{12} & a_{13} \\ a_{21} & a_{22} & a_{23} \end{array}\right)B=\left(\begin{array}{ll} b_{11} & b_{12} \\ b_{21} & b_{22} \\ b_{31} & b_{32} \end{array}\right) \\ 那么A*B=\left(\begin{array}{lll} a_{11} b_{11}+a_{12} b_{21}+a_{13} b_{31} & a_{11} b_{12}+a_{12} b_{22}+a_{13} b_{32} \\ a_{21} b_{11}+a_{22} b_{21}+a_{23} b_{31} & a_{21} b_{12}+a_{22} b_{22}+a_{23} b_{32} \end{array}\right) \end{array} A=(a11a21a12a22a13a23)B=b11b21b31b12b22b32AB=(a11b11+a12b21+a13b31a21b11+a22b21+a23b31a11b12+a12b22+a13b32a21b12+a22b22+a23b32)

一般,我们设 A = ( a i j ) A=(a_{ij}) A=(aij)是一个 m ∗ s m*s ms的矩阵, B = ( b i j ) B=(b_{ij}) B=(bij)是一个 s ∗ n s*n sn矩阵,那么规定矩阵 A A A与矩阵 B B B的乘积是一个 m ∗ n m*n mn的矩阵 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(n1)+F(n2)n=1n=2n3

如果我们要求某一项是要 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(n2)F(n1)][F(n1)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(n2)F(n1)][F(n1)F(n)]

F ( n ) F(n) F(n)拆成 F ( n − 1 ) + F ( n − 2 ) F(n-1)+F(n-2) F(n1)+F(n2)

[ 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(n2)F(n1)][F(n1)F(n1)+F(n2)]

然后稍微补充一下,构造完整的矩阵:

[ 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(n2)F(n1)][F(n1)×1+F(n2)×0F(n1)×1+F(n2)×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(n2)F(n1)]=[F(n2)×0+F(n1)×1F(n2)×1+F(n1)×1]=[F(n1)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(n2)F(n1)]=[F(n1)F(n)][0111]×[F(n1)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(n1)F(n)]=[0111]n2×[F(1)F(2)]

[ 0 1 1 1 ] n − 2 \left[\begin{array}{ll} 0 & 1 \\ 1 & 1 \end{array}\right]^{n-2} [0111]n2显然:矩阵快速幂!!!

只需要将上面的整数快速幂代码改成矩阵的即可了,详见代码:

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+1FnFnFn1]=[1110]n

#include <iostream>
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;
}

矩阵快速幂,简而言之就是利用矩阵加速数列递推,其中最难的也就是怎么推矩阵了,大家可以试一下下面几个例子哦:

  • F ( n ) = F ( n − 1 ) + F ( n − 2 ) + 1 F(n)=F(n-1)+F(n-2)+1 F(n)=F(n1)+F(n2)+1
  • F ( n ) = F ( n − 1 ) + F ( n − 3 ) F(n)=F(n-1)+F(n-3) F(n)=F(n1)+F(n3)
  • F ( n ) = F ( n − 1 ) + F ( n − 3 ) + F ( n − 4 ) F(n)=F(n-1)+F(n-3)+F(n-4) F(n)=F(n1)+F(n3)+F(n4)
  • F n = a × F n − 1 + b × F n − 2 F_{n}=a \times F_{n-1}+b \times F_{n-2} Fn=a×Fn1+b×Fn2
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

nuoyanli

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值