题目传送门

题目大意:计算数列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. 更换gcc工具链

    title: 更换gcc工具链 date: 2019/1/16 19:27:51 toc: true --- 更换gcc工具链 下载后解压到一个临时目录先看看文件结构 mkdir tmp tar xj ...

  2. DirectX11 With Windows SDK--06 DirectXMath数学库

    前言 xnamath.h原本是位于DirectX SDK的一个数学库,但是现在Windows SDK包含的数学库已经抛弃掉原来的xnamath.h,并演变成了现在的DirectXMath.h.其实本质 ...

  3. 关于使用python ~取反操作符带出的一系列问题

    晚上的时候,无意之间看到stackoverflow上面的一个编程挑战赛,各路高手各种搞事,看到python的地方突然发现用了很多位运算的符号,但是~符号引起了我和同事的注意. 我们很少在程序中使用这种 ...

  4. [物理学与PDEs]第2章第5节 一维流体力学方程组的 Lagrange 形式 5.2 Lagrange 坐标

    1. Lagrange 坐标 $$\beex \bea &\quad 0=\int_\Omega\cfrac{\p \rho}{\p t}+\cfrac{\p}{\p x}(\rho u)\r ...

  5. ve2.0 v-for循环报错的解决方案

    <li v-for="(item,index) in mokeData" class="page" :key="index"> ...

  6. Linux 一 些常用的命令

    查看当前系统JAVA的安装路径: echo $JAVA_HOME: 查看内核版本: uname -a ubuntu的防火墙 关闭:ufw disable开启:ufw enable 卸载了 iptabl ...

  7. 关于 min_25 筛的入门以及复杂度证明

    min_25 筛是由 min_25 大佬使用后普遍推广的一种新型算法,这个算法能在 \(O({n^{3\over 4}\over log~ n})\) 的复杂度内解决所有的积性函数前缀和求解问题(个人 ...

  8. vue脚手架安装步骤vue-cli

    1.环境搭建     安装node.js: 从node.js官网下载并安装node,安装过程很简单.  npm 版本需要大于 3.0,如果低于此版本需要升级它: # 查看版本 $ npm -v 2.3 ...

  9. P2947 [USACO09MAR]向右看齐Look Up--单调栈

    单调栈真的很好用呢! P2947 [USACO09MAR]向右看齐Look Up 题目描述 Farmer John's N (1 <= N <= 100,000) cows, conven ...

  10. 使用 wordcloud 构建词云图

    from wordcloud import WordCloudfrom matplotlib import pyplot as pltfrom PIL import Imageimport numpy ...