矩阵加速 学习笔记

矩阵加速 学习笔记

首先关于矩阵的一些基本常识在这篇博客里已经介绍的很清楚了,在这里不在赘述。

但是矩阵乘的图片是一定放的,因为比较重要好像只有矩阵乘,其实看了图片好像根本不用看博文

观察图片我们可以发现,对于一个新矩阵的 \(F[i][j]\) 其实就是第一个矩阵的第 \(i\) 行和对应第二个矩阵的 \(j\) 列的每一个数乘起来再相加。这也就解释了为什么只有大小为 \(n*m\)\(m*k\) 的矩阵才可以相乘,且新矩阵为大小为 \(n*k\) ,如果不是这样,就会出现对不上项的情况。

和实数一样,在定义了矩阵乘之后矩阵幂也就自然出来了,只有 \(n*n\) 的矩阵才可以不断连乘。

就像上面的新矩阵 \(n*k\) ,如果 \(n\) \(m\) \(k\) 不相等,那么就会发现没有旧矩阵和新矩阵可以不断相乘。

还有就是单位矩阵,任何矩阵乘这个矩阵都是其本身,就像实数里的 \(1\) 一样,定义上面的博文里已经很详细了,这里只是口糊一下,就是只有对角线元素是 \(1\) 其余元素都为 \(0\) 的矩阵。

P3390 【模板】矩阵快速幂

模板题,直接放代码

#include<bits/stdc++.h>
#define ll long long
const int N=110;
const ll MOD=1e9+7;
ll n,k;
struct Matrix{
    int n;
    ll a[N][N];
    inline void clear(int size)
    {
        n=size;
        memset(a,0,sizeof(a));
        return ;
    }
    inline void build(int size)
    {
        n=size;
        memset(a,0,sizeof(a));
        for (int i=1;i<=n;i++)
        a[i][i]=1;
        return ;
    }
    inline void out()
    {
        for (int i=1;i<=n;i++)
        {
            for (int j=1;j<=n;j++)
            printf("%lld ",a[i][j]);
            printf("\n");
        }
    }
};
inline Matrix operator * (Matrix a,Matrix b)
{
    Matrix c;
    c.clear(a.n);
    for (int k=1;k<=c.n;k++)
    for (int i=1;i<=c.n;i++)
    for (int j=1;j<=c.n;j++)
    c.a[i][j]=(c.a[i][j]+a.a[i][k]*b.a[k][j]%MOD)%MOD;
    return c;
}
inline Matrix Matrix_qpow(Matrix a,ll p)
{
    Matrix ans,base=a;
    ans.build(a.n);
    for (;p;p>>=1,base=base*base)
    if (p&1) ans=ans*base;
    return ans;
}
int main()
{
    scanf("%lld%lld",&n,&k);
    Matrix now;
    now.clear(n);
    for (int i=1;i<=n;i++)
    for (int j=1;j<=n;j++)
    scanf("%lld",&now.a[i][j]);
    Matrix_qpow(now,k).out();
    return 0;
}

需要注意的一个问题是如果存矩阵的结构体占用空间过大,当其传入自定义函数的时候会引起爆栈。

可以在编译命令里加入无限栈空间的命令。

-Wl,--stack=1234567890

那么说了那么多,矩阵乘和矩阵快速幂有什么用呢??

矩阵快速幂可以优化一些递推式,使 \(\Theta(n)\) 的时间复杂度变为 \(\Theta (\log_2n)\)

下面的题目就是一个比较简单的应用。

P1962 斐波那契数列

思路主要来自这篇博客 ,这里主要就是口述一下过程。

首先我们知道斐波那契数列 \(F[n]=F[n-1]+F[n-2]\) ,对于 \(F[n]\) 这和 \(F[n-1]\)\(F[n-2]\) 有关,我们设 \(Fib[n-1]\) 为一个 \(F[n-1]\)\(F[n-2]\) 的二元组,所以 \(Fib[n]\) 就是 \(F[n]\)\(F[n-1]\) ,考虑 \(Fib[n]\) 怎样由 \(Fib[n-1]\) 得到。我们发现

\[F[n]=F[n-1]*1+F[n-2]*1\]

\[F[n-1]=F[n-1]*1+F[n-2]*0\]

如果放了矩阵上,它就是这样的。

然后我们发现求第 \(n\) 项的过程其实就是不断进行上面的过程的过程,最终的答案就是求下面这个矩阵的第一行第一个元素

\[\left[\begin{array}{cc}{F_{n-1}} & {F_{n-2}}\end{array}\right] \times\left[\begin{array}{cc}{1} & {1} \\ {1} & {0}\end{array}\right]^{n-2}\]

因为矩阵满足结合律,所以我们就先用矩阵快速幂求出来\(\left[\begin{array}{ll}{1} & {1} \\ {1} & {0}\end{array}\right]^{n-2}\) ,然后再求 \(\left.[\begin{array}{ll}{1} & {1}\end{array}\right] \times\left[\begin{array}{ll}{1} & {1} \\ {1} & {0}\end{array}\right]^{n-2}\) ,需要注意的是矩阵并不满足交换律,所以不能换过来乘。

P1939 【模板】矩阵加速(数列)

和上面的那一道题目推法差不多,一般来说要求的项数前与 \(n\) 项相关,矩阵就是 \(n*n\) 的。

#include<bits/stdc++.h>
#define ll long long
const int N=12;
const ll MOD=1e9+7;
struct Matrix{
    int n;
    ll a[N][N];
    inline void clear(int sz)
    {
        n=sz;
        memset(a,0,sizeof(a));
        return ;
    }
    inline void build(int sz)
    {
        n=sz;
        memset(a,0,sizeof(a));
        for (int i=1;i<=n;i++) a[i][i]=1;
        return ;
    }
    inline void out()
    {
        for (int i=1;i<=n;i++)
        {
            for (int j=1;j<=n;j++)
            printf("%lld ",a[i][j]);
            printf("\n");
        }
        return ; 
    }
};
inline Matrix operator * (Matrix a,Matrix b)
{
    Matrix c;
    c.clear(a.n);
    for (int k=1;k<=c.n;k++)
    for (int i=1;i<=c.n;i++)
    for (int j=1;j<=c.n;j++)
    c.a[i][j]=(c.a[i][j]+a.a[i][k]*b.a[k][j]%MOD)%MOD;
    return c;
}
inline Matrix Matrix_qpow(Matrix a,ll p)
{
    Matrix ans,base=a;
    ans.build(a.n);
    for (;p;p>>=1,base=base*base)
    if (p&1) ans=ans*base;
    return ans;
}
int t;
int main()
{
    scanf("%d",&t);
    while (t--)
    {
        ll k;
        scanf("%lld",&k);
        if (k<=3)
        {
            printf("1\n");
            continue;
        }
        Matrix now,ans;
        now.clear(3);
        now.a[1][1]=1;now.a[1][2]=1;now.a[1][3]=0;
        now.a[2][1]=0;now.a[2][2]=0;now.a[2][3]=1;
        now.a[3][1]=1;now.a[3][2]=0;now.a[3][3]=0;
        ans=Matrix_qpow(now,k-3);
        printf("%lld\n",(ans.a[1][1]+ans.a[2][1]+ans.a[3][1])%MOD);
    }
    return 0;
}

不过在最后乘的时候一定要严格按照矩阵乘法的定义来,因为你永远不知道经过矩阵快速幂的矩阵是什么。

P1349 广义斐波那契数列

只是加了系数,和上面的推法一样,需要注意的是定义的递推矩阵和给的顺序正好相反。

#include<bits/stdc++.h>
#define ll long long
const int N=5;
struct Matrix{
    int n;
    ll a[N][N];
    inline void clear(int sz)
    {
        n=sz;
        memset(a,0,sizeof(a));
        return ;
    }
    inline void build(int sz)
    {
        n=sz;
        memset(a,0,sizeof(a));
        for (int i=1;i<=n;i++)
        a[i][i]=1;
        return ;
    }
};
ll q,p,a1,a2,n,mod;
inline Matrix operator * (Matrix a,Matrix b)
{
    Matrix c;
    c.clear(a.n);
    for (int k=1;k<=c.n;k++)
    for (int i=1;i<=c.n;i++)
    for (int j=1;j<=c.n;j++)
    c.a[i][j]=(c.a[i][j]+a.a[i][k]*b.a[k][j]%mod)%mod;
    return c;
}
inline Matrix Matrix_qpow(Matrix a,ll p)
{
    Matrix ans,base=a;
    ans.build(a.n);
    for (;p;p>>=1,base=base*base)
    if (p&1) ans=ans*base;
    return ans;
}
int main()
{
    scanf("%lld%lld%lld%lld%lld%lld",&p,&q,&a1,&a2,&n,&mod);
    Matrix now,ans;
    now.clear(2);
    now.a[1][1]=p;now.a[1][2]=1;
    now.a[2][1]=q;now.a[2][2]=0;
    ans=Matrix_qpow(now,n-2);
    printf("%lld\n",(ans.a[1][1]*a2%mod+ans.a[2][1]*a1%mod)%mod);
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/last-diary/p/11724424.html
今日推荐