模板

在使用的时候一定记得要初始化!!!

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. #define LL long long
  4. const int N = 110;
  5. const LL mod = 1e9 + 7;
  6. struct Matrix {
  7. int n;
  8. LL a[N][N];
  9. void init(int _n, int opt) {
  10. memset(a, 0, sizeof(a));
  11. n = _n;
  12. if (opt == 1)
  13. for (int i = 1; i <= n; ++i)
  14. a[i][i] = 1;
  15. }
  16. Matrix operator = (const Matrix &B) {
  17. this->n = B.n;
  18. for (int i = 1; i <= n; ++i)
  19. for (int j = 1; j <= n; ++j)
  20. this->a[i][j] = B.a[i][j];
  21. return *this;
  22. }
  23. Matrix operator * (const Matrix &B) const {
  24. Matrix res;
  25. res.init(n, 0);
  26. for (int i = 1; i <= n; ++i)
  27. for (int j = 1; j <= n; ++j)
  28. for (int k = 1; k <= n; ++k) {
  29. res.a[i][j] += (this->a[i][k]) * B.a[k][j];
  30. res.a[i][j] %= mod;
  31. }
  32. return res;
  33. }
  34. void output() {
  35. for (int i = 1; i <= n; ++i) {
  36. for (int j = 1; j < n; ++j)
  37. printf("%lld ", a[i][j]);
  38. printf("%lld\n", a[i][n]);
  39. }
  40. }
  41. };
  42. Matrix quickpow(Matrix P, LL n) {
  43. if (n == 1) return P;
  44. Matrix res;
  45. res.init(P.n, 1);
  46. while (n) {
  47. if (n & 1) res = res * P;
  48. n >>= 1;
  49. P = P * P;
  50. }
  51. return res;
  52. }
  53. int main()
  54. {
  55. int n;
  56. LL k;
  57. cin >> n >> k;
  58. Matrix A;
  59. A.init(n, 0);
  60. for (int i = 1; i <= n; ++i)
  61. for (int j = 1; j <= n; ++j)
  62. cin >> A.a[i][j];
  63. Matrix B = quickpow(A, k);
  64. B.output();
  65. return 0;
  66. }

应用:优化线性递推式

求斐波那契数列的第 矩阵快速幂 - 图1矩阵快速幂 - 图2。(矩阵快速幂 - 图3#card=math&code=f1%3Df_2%3D1%2Cf_n%3Df%7Bn-1%7D%2Bf_%7Bn-2%7D%28n%20%5Cgeq%203%29&id=YH9Lb)) 矩阵快速幂 - 图4

递推的复杂度是 矩阵快速幂 - 图5#card=math&code=O%28n%29&id=TCYlu),现在不能接受。

所有递推式都可以表示成矩阵乘法的形式,例如

矩阵快速幂 - 图6

不过这个显然没法进行矩阵快速幂,所以我们改一下,变成:

矩阵快速幂 - 图7

对于 矩阵快速幂 - 图8,有

矩阵快速幂 - 图9

有初状态 矩阵快速幂 - 图10,所以我们直接矩阵快速幂求出后面的矩阵,和初状态矩阵乘一下即可得出答案。

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. #define LL long long
  4. const int N = 5;
  5. const LL mod = 1e9 + 7;
  6. //略去Matrix和quickpow的部分,以下同理
  7. void output(const Matrix &ANS) {
  8. LL ans = ANS.a[1][1] + ANS.a[1][2] + ANS.a[1][3];
  9. cout << ans % mod << endl;
  10. }
  11. int main()
  12. {
  13. //init
  14. Matrix A;
  15. A.init(2, 0);
  16. A.a[1][1] = A.a[1][2] = A.a[2][1] = 1;
  17. //
  18. LL n;
  19. cin >> n;
  20. if (n <= 2) {
  21. puts("1");
  22. return 0;
  23. }
  24. Matrix P = quickpow(A, n - 2);
  25. output(P);
  26. return 0;
  27. }

不过不是所有递推式都这么简单,所以我们列出一些式子,来看看如何转换:

矩阵快速幂 - 图11

下面这个很奇妙,因为引入了除了 矩阵快速幂 - 图12 外,新的可变数列(这时我们需要导入常数来维护它):

矩阵快速幂 - 图13

应用:邻接矩阵的幂在图上的意义

我们记一个无边权无向图的邻接矩阵为 矩阵快速幂 - 图14矩阵快速幂 - 图15 表示两点间有一条边相连,反之则没有。
我们在Floyd算法中有这么一个松弛操作:

for (int k = 1; k <= n; ++k)
    for (int i = 1; i <= n; ++i)
        for (int j = 1; j <= n; ++j)
            if (d[i][j] > d[i][k] + d[k][j])
                d[i][j] = d[i][k] + d[k][j];

这是为了寻找中继点,尝试用来更新两点间的路径。我们惊奇发现,这个流程似乎和矩阵乘法十分相似。
如果我们将他稍微修改一下,变成这样:

int d[N][N], D[N][N];
for (int i = 1; i <= n; ++i)
    for (int j = 1; j <= n; ++j)
        for (int k = 1; k <= n; ++k)
            D[i][j] += d[i][k] * d[k][j];

我们稍加猜想,便可想到 矩阵快速幂 - 图16 存储的是经过两步,从点i走到点j的方案数。有趣的是,矩阵快速幂 - 图17 。实际上,矩阵快速幂 - 图18 也可以理解为经过一步,从点i走到点j的方案数。

而我们计算 矩阵快速幂 - 图19,就可以得到经过四步的方案数,矩阵快速幂 - 图20 则是经过三步的方案数,诸如此类(例如矩阵快速幂 - 图21,我们枚举 矩阵快速幂 - 图22,那么 矩阵快速幂 - 图23 便是走 矩阵快速幂 - 图24 步从 ik,再走 矩阵快速幂 - 图25 步从 kj的方案数,累加上去后,便得到了走 矩阵快速幂 - 图26 步以从 ij的方案数 )。

实际上,结合Floyd算法和矩阵乘法的思想,我们不难得出结论:记 矩阵快速幂 - 图27 为邻接矩阵,则 矩阵快速幂 - 图28 则记录了某个点走 矩阵快速幂 - 图29 步后到达另一个点的总方案个数。

有一个机器人在一张无边权无向图(nm 边)的起点1上。每过一秒钟,他便可以进行下面三个操作之一:

  1. 停在原地不动
  2. 走向一个相邻的点
  3. 自爆

问经过时间 矩阵快速幂 - 图30 后,机器人的行为方案总数有多少? 矩阵快速幂 - 图31 题目来源:P3758 [TJOI2017]可乐

如果只有第二个操作,那么就是一个板子题了:直接计算邻接矩阵的 t 次方,记为 矩阵快速幂 - 图32,然后统计 矩阵快速幂 - 图33 即可得出方案总数。

对于第一个操作,我们给每个点连上一个自环即可。
对于第三个操作,我们可以建一个新点,来表示这个死亡状态:每个点都向它连边,代表机器人在任意位置都可以自爆;这个点不向外连边,代表机器人自爆之后不可能复活,前往别的位置啥的。(但是自己要往自己连一条边,不然的话所花时间就没法保证完全为 矩阵快速幂 - 图34 了,不懂的话可以拿样例试一试)

#include<bits/stdc++.h>
using namespace std;
#define LL long long
const int N = 40;
const LL mod = 2017;
int main()
{
    Matrix A;
    int n, m, t;
    scanf("%d%d", &n, &m);
    A.init(n + 1, 1);
    for (int i = 1; i <= n; ++i)
        A.a[i][n + 1] = 1;
    for (int i = 1; i <= m; ++i) {
        int u, v;
        scanf("%d%d", &u, &v);
        A.a[u][v] = A.a[v][u] = 1;
    }
    scanf("%d", &t);
    Matrix D = quickpow(A, t);
    LL ans = 0;
    for (int i = 1; i <= n + 1; ++i)
        ans = (ans + D.a[1][i]) % mod;
    printf("%lld", ans);
    return 0;
}

应用:对DP进行加速

动态规划的求解形式有两种:一是记忆化搜索,二是递推。如果一个基于递推的 DP 方程中仅包含加减等基本运算,便可以使用矩阵乘法对其进行优化。

给定一个长度为 矩阵快速幂 - 图35 的数组,你现在需要将其分块。 小明要求每个块的长度必须在集合 矩阵快速幂 - 图36 种,小红要求每个块的长度必须在集合 矩阵快速幂 - 图37 中,问我们有多少种不同的分块方法? 记最大块长为 矩阵快速幂 - 图38,有 矩阵快速幂 - 图39

我们直接对两个集合求一下交集集合,方法随意(反正范围就这么点大)。
我们不妨记交集为 矩阵快速幂 - 图40,记 矩阵快速幂 - 图41 表示前 矩阵快速幂 - 图42 长度的数列的分块种类,那么有 DP 方程

矩阵快速幂 - 图43

如果线性递推,复杂度显然是 矩阵快速幂 - 图44#card=math&code=O%28nx%29&id=bjmYf) 的,直接 T 飞。不过,我们可以用一手矩阵快速幂来对他进行优化。
已知当 矩阵快速幂 - 图45 的时候,一整个求和式子都可以算满,所以我们先预处理出 0x-1 的初始向量 矩阵快速幂 - 图46 ,随后我们直接构造递推矩阵即可。其实并不是很难,照着集合 矩阵快速幂 - 图47 的情况递推即可。我们假设 矩阵快速幂 - 图48,那么便有 DP 方程:

矩阵快速幂 - 图49

矩阵快速幂进行优化,可以将复杂度降到 矩阵快速幂 - 图50#card=math&code=O%28x%5E3%5Clog%20n%29&id=SYIHO)。

#include<bits/stdc++.h>
using namespace std;
#define LL long long
const int N = 110;
const LL mod = 1e9 + 7;
namespace getSelf {
    int n, m, vis1[N], vis2[N];
    //范围比较小,可以通过开一个桶来求交集
    void getArr(int &cnt, int *arr) {
        memset(vis1, 0, sizeof(vis1));
        memset(vis2, 0, sizeof(vis2));
        scanf("%d", &n);
        for (int i = 0, x; i < n; ++i)
            scanf("%d", &x), vis1[x] = 1;
        scanf("%d", &m);
        for (int i = 0, x; i < m; ++i)
            scanf("%d", &x), vis2[x] = 1;
        for (int i = 1; i < N; ++i)
            if (vis1[i] && vis2[i]) arr[++cnt] = i;
    }
}
int main()
{
    //读入数据
    LL n;
    cin >> n;
    int m = 0, v[N];
    getSelf::getArr(m, v);
    //预处理DP
    int x = v[m];
    LL f[N]; memset(f, 0, sizeof(f)); f[0] = 1;
    for (int i = 1; i < x; ++i)
        for (int j = 1; j <= m; ++j)
            if (i >= v[j]) f[i] = (f[i] + f[i - v[j]]) % mod;
    //构造转移矩阵
    Matrix A;
    A.init(x, 0);
    for (int i = 1; i <= m; ++i) A.a[1][v[i]] = 1;
    for (int i = 2; i <= x; ++i) A.a[i][i - 1] = 1;
    Matrix B = quickpow(A, n - x + 1);
    //求和输出
    LL ans = 0;
    for (int i = 1; i <= x; ++i)
        ans = (ans + B.a[1][i] * f[x - i]) % mod;
    printf("%lld", ans);
    return 0;
}