0
点赞
收藏
分享

微信扫一扫

ACwing 闪烁 矩阵快速幂

前行的跋涉者 2022-01-14 阅读 23

题外话:本来要发在Acwing上的,但是实在不会用Acwing上的MARKDOWN,改发在CSDN上

矩阵快速幂

简单的介绍以下矩阵快速幂(也帮助自己回顾以下,已经会了的同学自行跳过)

原理:由递推关系提到一个递推矩阵C,初始状态为A,每次执行一次就相当于(注意C矩阵在前)
A = C ∗ A A=C*A A=CA
执行n次后的结果矩阵B就等于

B = C n ∗ A B=C^n*A B=CnA

自然而然的想到使用快速幂来对这个过程进行加速
算法共分为两个部分:如何得到递推矩阵和矩阵的快速幂,先从简单的快速幂介绍

快速幂

快速幂的原理不再赘述,y总算法基础课有讲,大家也可以自行百度。一般的快速幂如下:
##普通快速幂(c++)

int ksm(int a,int b)
{
    int res=1;
    while(b)
    {
        if(b&1) 
            res=res*a;
        a=a*a;
        b>>=1;
    }
    return res;
}

而矩阵快速幂中所需要做就是将整数之间的乘法转变为矩阵之间的乘法

矩阵乘法这里只提一点,第一个矩阵的列数等于第二个矩阵的行数,如
2×3的矩阵A只能与3×n的矩阵B相乘,结果矩阵C为2×n

转变的方法有两种

1.定义实现矩阵函数的函数;
2.重载矩阵之间的称号;
这里我用的是第二种
##贴以下自己的板子

struct mat{
	int n, m;
	ll c[20][20];
    /*
        可以没有debug函数
        纯粹是因为自己码力不够
        每次写快速幂都得调试半天
    */
	void debug()
	{
		puts("");
		for (int i = 1; i <= n;++i)
		{
			for (int j = 1; j <= m;++j)
				printf("%d ", c[i][j]);
			puts("");
		}
	}
};


mat operator*(mat &x,mat &y)
{
	mat res;
	res.n = x.n;
	res.m = y.m;
	for (int i = 1; i <= res.n;++i)
		for (int j = 1; j <= res.m;++j)
			res.c[i][j] = 0;
	for (int i = 1; i <= res.n;++i)
	{
		for (int j = 1; j <= res.m;++j)
		{
			for (int k = 1; k <= x.m;++k)
			{
				res.c[i][j] = (res.c[i][j] + x.c[i][k] * y.c[k][j] % mod) % mod;
			}
		}
	}
	return res;
}

这样就可以直接使用上面的快速幂的板子实现矩阵之间的快速幂,不同在于数据类型发生了改变

得到递推矩阵

这是算法中的难点,通过上面的矩阵快速幂模板,只要得到正确的递推矩阵,随便A。
先从简单的看起:斐波拉契数列,递推公式:
{ f ( n ) = f ( n − 1 ) + f ( n − 2 ) , n > = 2 f ( 1 ) = 1 , f ( 2 ) = 1 \begin{cases} f(n)=f(n-1)+f(n-2),n>=2\\f(1)=1,f(2)=1 \end{cases} {f(n)=f(n1)+f(n2),n>=2f(1)=1,f(2)=1
前两项都为1,所以,初始矩阵A:
( 1 1 ) \begin{pmatrix} 1\\1 \end{pmatrix} (11)
由递推关系可知当前项n为前两项n-1,n-2的和,第n-1项不变,所以,递推矩阵C为:
( 1 1 0 1 ) \begin{pmatrix} 1 &1\\ 0&1 \end{pmatrix} (1011)
这里对于初学者来说理解可能有点困难,可以试试执行一次C*A,即
( 1 ∗ 1 + 1 ∗ 1 = 2 0 ∗ 1 + 1 ∗ 1 = 1 ) \begin{pmatrix} 1*1+1*1=2\\0*1+1*1=1 \end{pmatrix} (11+11=201+11=1)
符合递推公式。
以上就是对矩阵快速幂简单介绍。
回到本题中

递推矩阵

从题目中可以得到如下结论:

第m秒,第n个灯泡的值等于第m-1秒,第n-1个灯泡的值与第m-1秒,第n个灯泡的值相异或
等价与
第m秒,第n个灯泡的值等于第m-1秒,第n-1个灯泡的值与第m-1秒,第n个灯泡的值在摸2意义下的加法
写成递推式:
f ( m , n ) = f ( m − 1 , n ) ⊕ f ( m − 1 , n − 1 ) ⇒ f ( m , n ) = f ( m − 1 , n ) + f ( m − 1 , n − 1 ) ( m o d 2 ) f(m,n)=f(m-1,n) \oplus f(m-1,n-1)\Rightarrow \\ f(m,n)=f(m-1,n)+f(m-1,n-1)\quad(mod\quad2) f(m,n)=f(m1,n)f(m1,n1)f(m,n)=f(m1,n)+f(m1,n1)(mod2)

由此得出递推矩阵位
( 1 0 0 . . . 1 1 1 0 . . . 0 0 1 1 . . . 0 ⋮ ⋮ ⋮ ⋮ ⋮ 0 0 . . . 1 1 ) \begin{pmatrix} 1&0&0&...&1\\ 1&1&0&...&0\\ 0&1&1&...&0\\ \vdots&\vdots&\vdots&\vdots&\vdots\\ 0&0&...&1&1 \end{pmatrix} 11000110001............11001
初始矩阵由题目给出

代码入下


#include<iostream>
#include<cstring>
using namespace std;
typedef long long ll;
const int N=20;

int n;
ll k;

struct mat{
    int n,m;
    int c[N][N];

    void debug()
    {
        for(int i=1;i<=n;++i)
        {
            for(int j=1;j<=m;++j)
                printf("%d ",c[i][j]);
            puts("");
        }
    }

};

mat operator*(mat &x,mat &y)
{
    mat res;
    res.n=x.n;
    res.m=y.m;
    for(int i=1;i<=res.n;++i)
        for(int j=1;j<=res.m;++j)
            res.c[i][j]=0;
    for(int i=1;i<=res.n;++i)
        for(int j=1;j<=res.m;++j)
            for(int k=1;k<=x.m;++k)
                res.c[i][j]=(res.c[i][j]^(x.c[i][k]*y.c[k][j]));
    return res;
}

void ksm()
{
    cin>>n>>k;
    mat res,a;
    res.n=n;
    res.m=1;
    a.n=a.m=n;
    for(int i=1;i<=n;++i)
        for(int j=1;j<=n;++j)
            a.c[i][j]=0;
    a.c[1][1]=1;
    a.c[1][n]=1;
    for(int i=2;i<=n;++i)
    {
        a.c[i][i-1]=1;
        a.c[i][i]=1;
    }
    for(int i=1;i<=n;++i)
        cin>>res.c[i][1];
    while(k)
    {
        if(k&1)
            res=a*res;
        a=a*a;
        k>>=1;
    }
    res.debug();
}

int main()
{
    ksm();
    return 0;
}

后话:可以直接使用异或运算来重载矩阵之间的乘法(位运算应该还是要快一些,只是数据量太小没有体现)
现在是2022/1/14的3:06分,加油吧,大家。

举报

相关推荐

0 条评论