题目传送门

题目大意:计算数列a的第n项,其中:

\[a[1] = a[2] = a[3] = 1
\]

\[a[i] = a[i-3] + a[i - 1]
\]

\[(n ≤ 2 \times 10^9)
\]

一般的递推是O(n)的,显然时间和空间都不能承受。

由于每一步递推都是相同的。这句话包含了2个层面:首先,递推式是相同的;其次,递推的条件也要是相同的。综合来说,每一步的递推都是相同的。这是应用矩阵加速递推的充分条件。

那么怎么进行矩阵加速呢?我们首先观察,第\(i\)项和哪些项有关? 与\(i-3\)和\(i-1\)优化,也就是和前3项有关。为了能够**仅仅利用矩阵就能推出\(a[i]\), 我们需要记录前3项,于是,我们构造一个3*3的矩阵:

\[A=\begin{bmatrix}
1 & 0 & 1 \\
1 & 0 & 0 \\
0 & 1 & 0 \\
\end{bmatrix}
\]

有同学会问:这个矩阵是怎么构造出来的呢?

我们首先要构造出类似于DP的状态来存下所有计算过程中可能会用到的信息,对于这道题,我们需要记录:(假设我们要从\(a[i]\)推到\(a[i+1]\))

\[B=\begin{bmatrix}
a[i] \\
a[i-1] \\
a[i-2] \\
\end{bmatrix}
\]

这个矩阵要推到:

\[C=\begin{bmatrix}
a[i+1] \\
a[i] \\
a[i-1] \\
\end{bmatrix}
\]

也就是说,我们需要构造一个矩阵\(A\),使得\(A*B = C\),根据线性代数的相关定义,A一定是一个\(3*3\)的矩阵,没错吧。

那好,我们已经得到:

\[\begin{bmatrix}
? & ? & ? \\
? & ? & ? \\
? & ? & ? \\
\end{bmatrix}*\begin{bmatrix}
a[i] \\
a[i-1] \\
a[i-2] \\
\end{bmatrix}
=\begin{bmatrix}
a[i+1] \\
a[i] \\
a[i-1] \\
\end{bmatrix}
\\A*B=C
\]

我们只需要根据递推式,把矩阵\(A\)中的数填满就可以了,比如说:

由于$a[i+1] = a[i-2] +a[i] \(,而根据矩阵,\)a[i+1] = (0,0) * a[i] + (0,1) * a[i-1] + (0,2) * a[i-2]$,所以,矩阵的第一行是\(1,0,1\),以此类推,就可以吧矩阵填满了。

然后,我们可以得到:

\[\begin{bmatrix}
1 & 0 & 1 \\
1 & 0 & 0 \\
0 & 1 & 0 \\
\end{bmatrix}*\begin{bmatrix}
a[i] \\
a[i-1] \\
a[i-2] \\
\end{bmatrix}
=\begin{bmatrix}
a[i+1] \\
a[i] \\
a[i-1] \\
\end{bmatrix}
\\A*B=C
\]

可是,有了这个,怎么从\(a[1]\)推到\(a[n]\)呢?

我们知道:\(a[1] = a[2] = a[3] = 1\),如果把它们代入矩阵\(B\)(就是中间的那个矩阵),我们会得到:

\[\begin{bmatrix}
1 & 0 & 1 \\
1 & 0 & 0 \\
0 & 1 & 0 \\
\end{bmatrix}*\begin{bmatrix}
a[3]=1 \\
a[2]=1 \\
a[1]=1 \\
\end{bmatrix}
=\begin{bmatrix}
a[4]=1*a[3] + 1 * a[1] = 2 \\
a[3] = 1 \\
a[2] = 1 \\
\end{bmatrix}
\\A * B = C
\]

一开始我们只知道\(a[1], a[2], a[3]\),但是两个矩阵相乘后,我们得到了一个新的值\(a[4] = 2\),很开心有木有。如果我们用矩阵\(A\)去乘矩阵\(C\),会得到一个新的矩阵,暂且叫\(C'\),你会得到有一个新的值\(a[5]\),我们有点兴奋起来,这有点想多米诺骨牌,推到第一个,会一直向前倒,知道最后一个。我相信你脑子一定有了这样一个式子:

\[……
\begin{bmatrix}
1 & 0 & 1 \\
1 & 0 & 0 \\
0 & 1 & 0 \\
\end{bmatrix}*
\begin{bmatrix}
1 & 0 & 1 \\
1 & 0 & 0 \\
0 & 1 & 0 \\
\end{bmatrix}*
\begin{bmatrix}
1 & 0 & 1 \\
1 & 0 & 0 \\
0 & 1 & 0 \\
\end{bmatrix}*\begin{bmatrix}
a[3]=1 \\
a[2]=1 \\
a[1]=1 \\
\end{bmatrix}
=\begin{bmatrix}
a[n] \\
a[n-1] \\
a[n-2] \\
\end{bmatrix}
\]

矩阵乘法有结合律(但没有交换律,不要问我为什么),所以左边一堆相同的矩阵(不妨叫系数矩阵)可以用一个括号括起来,就像这样:

\[\left(……
\begin{bmatrix}
1 & 0 & 1 \\
1 & 0 & 0 \\
0 & 1 & 0 \\
\end{bmatrix}*
\begin{bmatrix}
1 & 0 & 1 \\
1 & 0 & 0 \\
0 & 1 & 0 \\
\end{bmatrix}*
\begin{bmatrix}
1 & 0 & 1 \\
1 & 0 & 0 \\
0 & 1 & 0 \\
\end{bmatrix}\right)*\begin{bmatrix}
a[3]=1 \\
a[2]=1 \\
a[1]=1 \\
\end{bmatrix}
=\begin{bmatrix}
a[n] \\
a[n-1] \\
a[n-2] \\
\end{bmatrix}
\]

想到了什么?

\[\begin{bmatrix}
1 & 0 & 1 \\
1 & 0 & 0 \\
0 & 1 & 0 \\
\end{bmatrix}^k*\begin{bmatrix}
a[3]=1 \\
a[2]=1 \\
a[1]=1 \\
\end{bmatrix}
=\begin{bmatrix}
a[n] \\
a[n-1] \\
a[n-2] \\
\end{bmatrix}
\]

我们可以得到\(k = n - 3\)(想想为什么?),由于n很大,能不能快速求矩阵k次方呢?

在mod p意义下?矩阵乘法满足结合律?

快速幂!

为什么可以用快速幂这个黑科技呢?

普通的快速幂是用来求\(b^k mod\ p\)的,其原理是把\(k\)二进制拆分成\(k=2^{a_1}+2^{a_2}+ ……+2^{a_k}\),从而得到\(b^k mod \ p = b^{2^{a_1}} * b^{2^{a_2}} * ……*b^{2^{a_k}} mod \ p = ((b^{2^{a_1}} mod \ p) *(b^{2^{a_2}} mod \ p) * …… * (b^{2^{a_k}} mod \ p))\ mod\ p\) ,只要满足乘法结合律,就可以进行快速幂。

矩阵快速幂通常用来加速递推。比如说斐波那契数列的第n项mod p的值也可以用矩阵快速幂来求。但是并不是所有的递推都可以用矩阵快速幂,只有那些转移具有周期性的递推才能使用。

代码模块

1、矩阵的定义(结构体)

struct Square{
int mat[3][3];
void clear(){
memset(mat, 0, sizeof(mat));
}
} Base, Result;

2、方阵的乘法

void Times(Square &A, Square B){
Square C; C.clear();
for (int i = 0; i <= 2; ++i)
for (int j = 0; j <= 2; ++j)
for (int k = 0; k <= 2; ++k)
(C.mat[i][j] += (LL)A.mat[i][k] * B.mat[k][j] % p) %= p;
A = C;
}

3、矩阵快速幂

void SquareQpow(Square Base, int k){
if (k == 1){
Result = Base;
return;
}
Result.clear();
SquareQpow(Base, k / 2);
Times(Result, Result);
if (k % 2 == 1) Times(Result, Base);
}

4、矩阵初始化

void init(){
Base.clear();
Base.mat[0][0] = 1; Base.mat[0][2] = 1;
Base.mat[1][0] = 1; Base.mat[2][1] = 1;
}

易错点

  1. 乘法时需进行强制类型转换:(C.mat[i][j] += (LL)A.mat[i][k] * B.mat[k][j] % p) %= p;
  2. C++数组从0开始的哦QAQ
  3. 计算答案时注意要加3项,不要只加2项

完整代码

#include<iostream>
#include<cstdio>
#include<cstring>
#define LL long long
using namespace std;
const int p = 1e9 + 7;
struct Square{
int mat[3][3];
void clear(){
memset(mat, 0, sizeof(mat));
}
} Base, Result; void init(){
Base.clear();
Base.mat[0][0] = 1; Base.mat[0][2] = 1;
Base.mat[1][0] = 1; Base.mat[2][1] = 1;
} void Times(Square &A, Square B){
Square C; C.clear();
for (int i = 0; i <= 2; ++i)
for (int j = 0; j <= 2; ++j)
for (int k = 0; k <= 2; ++k)
(C.mat[i][j] += (LL)A.mat[i][k] * B.mat[k][j] % p) %= p;
A = C;
} void SquareQpow(Square Base, int k){
if (k == 1){
Result = Base;
return;
}
Result.clear();
SquareQpow(Base, k / 2);
Times(Result, Result);
if (k % 2 == 1) Times(Result, Base);
} int main(){
int T; scanf("%d", &T);
while (T--){
int n;
scanf("%d", &n);
if (n <= 3) printf("1\n");
else{
init();
SquareQpow(Base, n-3);
printf("%d\n", ((Result.mat[0][0] + Result.mat[0][1]) % p + Result.mat[0][2]) % p);
}
}
return 0;
}

[模板][题解][Luogu1939]矩阵乘法加速递推(详解)的更多相关文章

  1. HDU 5863 cjj's string game (矩阵乘法优化递推)

    题目大意:用k种字符构建两个长度为n的字符串(每种字符有无限多个),要求对应位置字符相同的连续子串最长长度为m,问方法数. 其中k,n,m是输入,n(1<=n<=1000000000), ...

  2. 4-2.矩阵乘法的Strassen算法详解

    题目描述 请编程实现矩阵乘法,并考虑当矩阵规模较大时的优化方法. 思路分析 根据wikipedia上的介绍:两个矩阵的乘法仅当第一个矩阵B的列数和另一个矩阵A的行数相等时才能定义.如A是m×n矩阵和B ...

  3. BZOJ 4870: [Shoi2017]组合数问题 矩阵乘法_递推

    Code: #include <cstdio> #include <cstring> #include <algorithm> #define setIO(s) f ...

  4. 母牛的故事(hdoj 2018,动态规划递推,详解)

    有一头母牛,它每年年初生一头小母牛.每头小母牛从第四个年头开始,每年年初也生一头小母牛.请编程实现在第n年的时候,共有多少头母牛? Sample Input2450Sample Output246 / ...

  5. POJ3070 Fibonacci(矩阵快速幂加速递推)【模板题】

    题目链接:传送门 题目大意: 求斐波那契数列第n项F(n). (F(0) = 0, F(1) = 1, 0 ≤ n ≤ 109) 思路: 用矩阵乘法加速递推. 算法竞赛进阶指南的模板: #includ ...

  6. luogu题解 P1707 【刷题比赛】矩阵加速递推

    题目链接: https://www.luogu.org/problemnew/show/P1707 分析: 洛谷的一道原创题,对于练习矩阵加速递推非常不错. 首先我们看一下递推式: \(a[k+2]= ...

  7. HDU 5950 - Recursive sequence - [矩阵快速幂加速递推][2016ACM/ICPC亚洲区沈阳站 Problem C]

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5950 Farmer John likes to play mathematics games with ...

  8. CH 3401 - 石头游戏 - [矩阵快速幂加速递推]

    题目链接:传送门 描述石头游戏在一个 $n$ 行 $m$ 列 ($1 \le n,m \le 8$) 的网格上进行,每个格子对应一种操作序列,操作序列至多有 $10$ 种,分别用 $0 \sim 9$ ...

  9. bzoj2004公交线路——DP+矩阵加速递推

    题目:https://www.lydsy.com/JudgeOnline/problem.php?id=2004 求方案数,想到DP: 因为两个站间距离<=p,所以每p个站中所有车一定都会停靠至 ...

随机推荐

  1. Redis扩展机制

    Redis扩展机制扫盲 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 关于Redis的Avalibility解决方案有很多,比如Twemproxy,Codis, 一.Twempro ...

  2. Oracle Database 10g安装

    前言 oracle_10g_32位的安装包 链接:https://pan.baidu.com/s/1v1ZWYSjWLzKo3GnDuV5nrg 密码:88yd PLSQL Developer 32位 ...

  3. java调用matlab绘图

    一 注意事项 1: MatLab的版本必须是2006b+(包括2006b或更高版本),因为只有在这些版本中才有MATLAB Builder for Java(也叫Java Builder). 2: 运 ...

  4. mysql 端口修改

    mysql 修改端口 1.  停止mysql服务 2.  打开文件夹下my.ini文件.(E:\mysql-5.7-3307) 修改文件中的port值,注意两个地方: [client]default- ...

  5. Java多线程:向线程传递参数的三种方法

    在传统的同步开发模式下,当我们调用一个函数时,通过这个函数的参数将数据传入,并通过这个函数的返回值来返回最终的计算结果.但在多线程的异步开发模式下,数据的传递和返回和同步开发模式有很大的区别.由于线程 ...

  6. 程序设计-理解java继承-遁地龙卷风

    (0)写在前面 编程和现实世界是息息相关的,你是如何理解现实世界中的继承呢? 继承在编程中应理解为:对父类资源(本文只讨论方法)的使用,父类方法只提供类基本的功能,父类方法之间不存在调用关系. (1) ...

  7. 微信app支付的坑

    app支付商户申请,需注册并认证开放平台账号后电脑端登录开放平台官网:open.weixin.qq.com,[管理中心]->[移动应用],选择需要申请支付的应用,点击[查看]->[微信支付 ...

  8. SQL Server - AS

    AS 是给现有的字段名/表名指定一个别名的意思.

  9. python图片转为base64

    # -*- coding: utf-8 -*- import base64 with open("/home/chaowei/1.png","rb") as f ...

  10. 【转】浅析Java中的final关键字

    谈到final关键字,想必很多人都不陌生,在使用匿名内部类的时候可能会经常用到final关键字.另外,Java中的String类就是一个final类,那么今天我们就来了解final这个关键字的用法. ...