题目传送门

题目大意:计算数列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. 我眼里K-Means算法

    在我眼里一切都是那么简单,复杂的我也看不懂,最讨厌那些复杂的人际关系,唉,像孩子一样交流不好吗. 学习K-Means算法时,会让我想起三国志这个游戏,界面是一张中国地图,诸侯分立,各自为据.但是游戏开 ...

  2. 浏览器UI多线程及JavaScript单线程运行机制的理解

    在上一篇博客中,我对jQuery的队列(queue)机制和动画(animate)机制做了一个深入的解析,在animate的实现机制其核心是依靠queue来完成的,其中在jQuery的链式调用部分,之前 ...

  3. 第九节: EF的性能篇(二) 之 Z.EntityFramework.Extensions程序集解决EF的性能问题

    一. 综述 该模块主要介绍:EF的性能优化插件Z.EntityFramework.Extensions,该插件收费. (一). 简介 1. 相关网站:http://www.zzzprojects.co ...

  4. django - 总结 - cnblog 知识点

    1.图像预览 点击头像------>点击input img和input重合; img在label,input-->display:none $("#avatar").c ...

  5. css 兼容各种iPhone

    @media (device-height:480px) and (-webkit-min-device-pixel-ratio:2){/* 兼容iphone4/4s */ .class{} } @m ...

  6. 二进制中连续k个1-题解

    原题传送门[>XJOI<]    重要提示:您的等级必须达到三级五段,否则会被一只小猫痛扁 题目描述: 求最小的m,使得m>=n而且m的二进制表示包含至少连续k个1 输入格式: 输入 ...

  7. linux文件系统初始化过程(4)---加载initrd(中)

    一.目的 上文详细介绍了CPIO格式的initrd文件,本文从源代码角度分析加载并解析initrd文件的过程. initrd文件和linux内核一般存储在磁盘空间中,在系统启动阶段由bootload负 ...

  8. tomcat 优化建议

    下面给出的是tomcat的优化建议,如果不同意见请留言. 上配置: tomcat jmx配置访问:修改catalina.sh CATALINA_OPTS="$CATALINA_OPTS -D ...

  9. 【原创】大数据基础之ElasticSearch(4)es数据导入过程

    1 准备analyzer 内置analyzer 参考:https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis- ...

  10. SpringBoot 整合 Dubbo 进行分布式开发

    自从Dubbo支持SpringBoot后,Dubbo与Spring的整合变得更加的简单了,下面就是完整的步骤: 1. 引入依赖 <dependency> <groupId>co ...