[关闭]
@Gary-Ying 2018-08-08T04:32:03.000000Z 字数 3992 阅读 1320

[模板][题解][Luogu1939]矩阵乘法加速递推

解题报告 算法


题目传送门

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

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

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

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


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

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


这个矩阵要推到:

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

那好,我们已经得到:


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

由于,而根据矩阵,,所以,矩阵的第一行是,以此类推,就可以吧矩阵填满了。

然后,我们可以得到:


可是,有了这个,怎么从推到呢?

我们知道:,如果把它们代入矩阵(就是中间的那个矩阵),我们会得到:


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

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

想到了什么?

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

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

快速幂!

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

普通的快速幂是用来求的,其原理是把二进制拆分成,从而得到 ,只要满足乘法结合律,就可以进行快速幂。

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

代码模块

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

  1. struct Square{
  2. int mat[3][3];
  3. void clear(){
  4. memset(mat, 0, sizeof(mat));
  5. }
  6. } Base, Result;

2、方阵的乘法

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

3、矩阵快速幂

  1. void SquareQpow(Square Base, int k){
  2. if (k == 1){
  3. Result = Base;
  4. return;
  5. }
  6. Result.clear();
  7. SquareQpow(Base, k / 2);
  8. Times(Result, Result);
  9. if (k % 2 == 1) Times(Result, Base);
  10. }

4、矩阵初始化

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

易错点

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

完整代码

  1. #include<iostream>
  2. #include<cstdio>
  3. #include<cstring>
  4. #define LL long long
  5. using namespace std;
  6. const int p = 1e9 + 7;
  7. struct Square{
  8. int mat[3][3];
  9. void clear(){
  10. memset(mat, 0, sizeof(mat));
  11. }
  12. } Base, Result;
  13. void init(){
  14. Base.clear();
  15. Base.mat[0][0] = 1; Base.mat[0][2] = 1;
  16. Base.mat[1][0] = 1; Base.mat[2][1] = 1;
  17. }
  18. void Times(Square &A, Square B){
  19. Square C; C.clear();
  20. for (int i = 0; i <= 2; ++i)
  21. for (int j = 0; j <= 2; ++j)
  22. for (int k = 0; k <= 2; ++k)
  23. (C.mat[i][j] += (LL)A.mat[i][k] * B.mat[k][j] % p) %= p;
  24. A = C;
  25. }
  26. void SquareQpow(Square Base, int k){
  27. if (k == 1){
  28. Result = Base;
  29. return;
  30. }
  31. Result.clear();
  32. SquareQpow(Base, k / 2);
  33. Times(Result, Result);
  34. if (k % 2 == 1) Times(Result, Base);
  35. }
  36. int main(){
  37. int T; scanf("%d", &T);
  38. while (T--){
  39. int n;
  40. scanf("%d", &n);
  41. if (n <= 3) printf("1\n");
  42. else{
  43. init();
  44. SquareQpow(Base, n-3);
  45. printf("%d\n", ((Result.mat[0][0] + Result.mat[0][1]) % p + Result.mat[0][2]) % p);
  46. }
  47. }
  48. return 0;
  49. }
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注