矩阵快速幂

矩阵快速幂其实并不是一个很难的算法,相对来说其实还算是比较简单,但是初看可能会难以理解他到底在干什么,其实有些我们只要动手模拟模拟就好,还有就是公式比较重要,看到有些博客拿斐波那契数列举例子,但是又不给出公式,很是让人头疼。

 

1.快速幂

在了解矩阵快速幂之前我们需要先了解快速幂。何为快速幂?即加快求幂运算的一种方式。

例如让我们求解2的7次方,我们之前的算法是将2重复的乘7次,也就是循环7次,但是有了矩阵快速幂之后,可以将循环次数减少到三次。我们将2的7次方进行分解,分解为2*(2*2)*(2*2*2*2)。可以划分为4个2* 2个2 * 1个2,这刚好与我们7的二进制的每一位数字对应,也就是111,我们知道了两个2相乘的结果,那么利用两个2相乘的结果去求解4个2相乘就只需要乘1次。先说到这里,剩下的看代码之后应该就能理解其中的含义了。

// n代表几次幂
int pow(int a,int n){
    int b = 1;
    while(n){
        if(n&1){
            b *= a;
        }
        // a进行翻倍,这样就能少乘几次了
        a *= a;
        n >>= 1;
    }
    return b;
}

上面的7次可能会有点特殊,因为其二进制的每一位都是1。可以测试一下8次吧,8的二进制只有第4位才为1。

 

2.矩阵快速幂

这里的矩阵就是我们线性代数中的矩阵,看这个之前必须要先了解线性代数中的矩阵乘法,这个就自行百度吧。

我们还是以斐波那契数列数列来讲解,首先要给出公式,斐波那契数列的递推公式为f[i]=f[i-1]+f[i-2],我们如何用矩阵乘法表示它呢?

\begin{pmatrix} f[i] & 0\\ f[i-1] & 0 \end{pmatrix}=\begin{pmatrix} 1 &1 \\ 1& 0 \end{pmatrix}*\begin{pmatrix} f[i-1] & 0\\ f[i-2] & 0 \end{pmatrix}

我们知道了矩阵的乘法,然后我们就知道上面f[i] = 1 * f[i-1] + 1 * f[i-2],这与我们的斐波那契数列公式是不是一模一样,再往下看

\begin{pmatrix} f[n] & 0\\ f[n-1] & 0 \end{pmatrix}=\begin{pmatrix} 1 &1 \\ 1& 0 \end{pmatrix}^{n-2}*\begin{pmatrix} f[1] & 0\\ f[2] & 0 \end{pmatrix}

有了这个式子,我们只要算出\begin{pmatrix} 1 &1 \\ 1& 0 \end{pmatrix}^{n-2}即可直接由f[1]f[2]求出f[n]。那么怎么快速计算出\begin{pmatrix} 1 &1 \\ 1& 0 \end{pmatrix}^{n-2}呢?快速幂即可!但有个需要注意的地方:当指数为零时,如果是数字,a^0=1,但矩阵应该怎么办呢?我们需要用到神奇的单位矩阵,它的主对角线上的数字为1,其它都为0,它有个有用的性质:任何矩阵乘以它都会等于本身。

其实你可能会有所疑问,为什么算出\begin{pmatrix} 1 &1 \\ 1& 0 \end{pmatrix}^{n-2}就可以知道f(n)的结果,因为f(n)就是前面无数个f(2)和f(1)的和,算矩阵乘法的过程中其实就是一直去加f(2)+f(1),快速幂矩阵就是能一次就很多个f(2)和f(1),最终得到f(n),可以先简单这样理解吧,剩下的还是要看代码。看代码的过程记得带上面的公式

代码实现:

#define N 2

int mod=10000;
int res[N][N];
int temp[N][N];
int a[N][N];
// 线性代数矩阵乘法
void Mul(int a[][N],int b[][N])
{
    memset(temp,0,sizeof(temp));
    for(int i=0; i<N; i++)
        for(int j=0; j<N; j++)
            for(int k=0; k<N; k++)
                temp[i][j]=(temp[i][j]+a[i][k]*b[k][j])%mod;
    for(int i=0; i<N; i++)
        for(int j=0; j<N; j++)
            a[i][j]=temp[i][j];
}
void fun(int a[][N],int n)
{
    memset(res,0,sizeof(res));
    // 单位矩阵初始化,单位矩阵与我们的数字1是一种概念,任意一个矩阵乘单位矩阵都不会变化
    for(int i=0; i<N; i++)
        res[i][i]=1;
    // 快速幂
    while(n)
    {
        if(n&1)
        {
            Mul(res,a);
        }
        Mul(a,a);
        n>>=1;
    }
}

int main()
{
    printf("%d",pow(2,31)-1);
    int n;
    while(~scanf("%d",&n)&&n!=-1)
    {
        // 与我们上面给定的公式是一样的
        // 这里的值只是普通的斐波那契是这个样子
        // 如果矩阵的对应关系不一样,矩阵中的值会有所变化
        // 其实知道原理之后,有些值还是很好推
        a[0][0]=1;
        a[1][0]=1;
        a[0][1]=1;
        a[1][1]=0;
        if(n==0||n==1)
            printf("%d\n",n);
        else{
            // n代表的是斐波那契数列的第几个
            // 注意下标开始的位置
            fun(a,n);
            printf("%d\n",res[1][0]);
        }
    }
}

3.小应用

上面的斐波那契算是一个经典的应用,下面这个题也是一个经典的应用。

HDU 2157:http://acm.hdu.edu.cn/showproblem.php?pid=2157

一看是个图论问题,刚开始看这个题真没发现与矩阵快速幂有多大的关系。

简述:给你一个n个点,给你然后告诉你哪个点能到哪个点,注意是有向的,最后给你两个点,问你从点a到点b经过k个点有多少种方案,注意:可走重复边,即一个边可以经过多次。

首先将给定的有向图转换为邻接矩阵,这就得到了我们快速幂矩阵中的矩阵

假设给定了4个点,让我们求0到3这个点有多少方案,那么点i到点j经过两个点的方案就有f(i,j) = f (i,k) + f(k,j) k是中间点,i,j是起点和终点,我们就需要去枚举这个中间点。枚举的过程就是矩阵的乘法的过程。三个点的方案也就用之前两个点去乘,因为路径可以重复走,如果不懂的话,就将邻接矩阵画出来模拟一下吧。

代码实现:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define N 21

int mod = 1000;
int map[N][N];
int temp[N][N];
int ts[N][N];
int res[N][N];

void matrix(int a[][N],int b[][N],int n){
    for(int i=0;i<n;i++){
        for(int j=0;j<n;j++){
            temp[i][j] = 0;
            for(int k=0;k<n;k++){
                temp[i][j] = (temp[i][j] + a[i][k] * b[k][j])%mod;
            }
        }
    }

    for(int i=0;i<n;i++){
        for(int j=0;j<n;j++){
            a[i][j] = temp[i][j];
        }
    }
}

void quickPow(int a[][N],int n,int size){
    memset(res,0,sizeof(res));
    for(int i=0;i<size;i++){
        res[i][i] = 1;
    }
    while(n){
        if(n&1){
            matrix(res,a,size);
        }
        matrix(a,a,size);
        n >>= 1;
    }
}

int main()
{
    int n,m,x,y,t,k;
    while(scanf("%d%d",&n,&m) == 2 && (n+m)){
        memset(map,0,sizeof(map));
        for(int i=0;i<m;i++){
            scanf("%d%d",&x,&y);
            map[x][y] = 1;
        }
        scanf("%d",&t);
        for(int i=0;i<t;i++){
            scanf("%d%d%d",&x,&y,&k);
            memcpy(ts,map,sizeof(map));
            quickPow(ts,k,n);
            printf("%d\n",res[x][y]);
        }
    }
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值