[关闭]
@RabbitHu 2017-08-03T01:48:55.000000Z 字数 4332 阅读 1867

矩阵乘法的4个有趣应用

笔记


目录


Vijos 1049 送给圣诞夜的礼品

有一个编号为 1~n 的序列,提供 m 个操作,每个操作是一个序列a,表示将当前序列中的a[i]个数放在第i个位置上。从头至尾循环执行这m个操作,直到共执行了k个操作。求最后得到的序列。

~ 矩阵乘法。

0 0 1 0       1       3
1 0 0 0   *   2   =   1
0 0 0 1       3       4
0 1 0 0       4       2

首先把m个操作乘成一个矩阵,然后用快速幂求出它的 k/m 次方,再乘上剩下的前 k%m 个矩阵,得到的就是总的操作,最后再乘上 1~n 竖排排列的一个原始矩阵,得到的就是最终结果。

  1. #include <cstdio>
  2. #include <cmath>
  3. #include <cstring>
  4. #include <algorithm>
  5. #include <iostream>
  6. using namespace std;
  7. const int MAXN = 105;
  8. int n, m, k;
  9. struct matrix {
  10. int g[MAXN][MAXN];
  11. matrix(){
  12. memset(g, 0, sizeof(g));
  13. }
  14. matrix operator * (matrix b) {
  15. matrix c;
  16. for(int k = 1; k <= n; k++)
  17. for(int i = 1; i <= n; i++)
  18. for(int j = 1; j <= n; j++)
  19. c.g[i][j] += g[i][k] * b.g[k][j];
  20. return c;
  21. }
  22. } one, op, tp, rest, ans;
  23. void init(){
  24. for(int i = 1; i <= n; i++)
  25. one.g[i][i] = 1;
  26. op = rest = one;
  27. for(int i = 1; i <= n; i++)
  28. ans.g[i][1] = i;
  29. }
  30. matrix qpow(matrix a, int x){
  31. if(x == 0) return one;
  32. matrix t = qpow(a, x >> 1);
  33. if(x & 1) return t * t * a;
  34. return t * t;
  35. }
  36. int main(){
  37. scanf("%d%d%d", &n, &m, &k);
  38. init();
  39. for(int i = 1; i <= m; i++){
  40. memset(tp.g, 0, sizeof(tp.g));
  41. int v;
  42. for(int j = 1; j <= n; j++){
  43. scanf("%d", &v);
  44. tp.g[j][v] = 1;
  45. }
  46. op = tp * op;
  47. if(i <= k % m)
  48. rest = tp * rest;
  49. }
  50. op = qpow(op, k / m);
  51. ans = rest * op * ans;
  52. for(int i = 1; i <= n; i++){
  53. if(i > 1) printf(" ");
  54. printf("%d", ans.g[i][1]);
  55. }
  56. printf("\n");
  57. return 0;
  58. }

@ 矩阵乘法注意顺序。

Vijos 1067 Warcraft III 守望者的烦恼

有一排格子,一开始在第 0 个格子,每次可以往右走 1~k 步,最后都要到达第 n 个格子。问有多少种方案?

~ 类似斐波那契!

首先我们知道状态转移方程:

构造一个这样的矩阵:

0 1 0 0 0       dp[i]       dp[i+1]
0 0 1 0 0       dp[i+1]     dp[i+2]
0 0 0 1 0   *   dp[i+2]  =  dp[i+3]
0 0 0 0 1       dp[i+3]     dp[i+4]
1 1 1 1 1       dp[i+4]     dp[i+5]

求出左边那个矩阵的 m 次方就好了!

事实上,许多递推式都可以写成矩阵乘法,例如:

……的矩阵可以写成:

0 1 0 0
0 0 1 0
0 0 0 1
1 3 5 -2

这些矩阵共同特点是右上角 (n-1)*(n-1) 的小矩阵的主对角线为 1,最后一行为递推式中对应的系数。

  1. #include <cstdio>
  2. #include <cmath>
  3. #include <cstring>
  4. #include <algorithm>
  5. #include <iostream>
  6. using namespace std;
  7. typedef long long ll;
  8. const int MO = 7777777, MAXN = 12;
  9. int n, m; //n是技能等级, m是监狱个数
  10. struct matrix {
  11. ll g[MAXN][MAXN];
  12. matrix(){
  13. memset(g, 0, sizeof(g));
  14. }
  15. matrix operator * (matrix b) {
  16. matrix c;
  17. for(int k = 1; k <= n; k++)
  18. for(int i = 1; i <= n; i++)
  19. for(int j = 1; j <= n; j++)
  20. c.g[i][j] = (c.g[i][j] + g[i][k] * b.g[k][j] % MO) % MO;
  21. return c;
  22. }
  23. } op, ans, one;
  24. matrix qpow(matrix a, int x){
  25. if(x == 0) return one;
  26. matrix t = qpow(a, x >> 1);
  27. if(x & 1) return t * t * a;
  28. return t * t;
  29. }
  30. void init(){
  31. for(int i = 1; i <= n; i++)
  32. one.g[i][i] = 1;
  33. for(int i = 1; i < n; i++)
  34. op.g[i][i + 1] = 1;
  35. for(int i = 1; i <= n; i++)
  36. op.g[n][i] = 1;
  37. ans.g[n][1] = 1;
  38. }
  39. int main(){
  40. scanf("%d%d", &n, &m);
  41. init();
  42. op = qpow(op, m);
  43. ans = op * ans;
  44. printf("%lld\n", ans.g[n][1]);
  45. return 0;
  46. }

Vijos 1603 迷宫

水题:有向图,求两点之间恰好走k条边能到达的路径数目。

~
一个结论:k条边路径数目 = 邻接矩阵的k次方中对应的路径数目

  1. #include <cstdio>
  2. #include <cmath>
  3. #include <cstring>
  4. #include <algorithm>
  5. #include <iostream>
  6. using namespace std;
  7. typedef long long ll;
  8. const int MAXN = 55;
  9. int n, s, f, m, p;
  10. struct matrix {
  11. ll g[MAXN][MAXN];
  12. matrix(){
  13. memset(g, 0, sizeof(g));
  14. }
  15. matrix operator * (matrix b) {
  16. matrix c;
  17. for(int k = 1; k <= n; k++)
  18. for(int i = 1; i <= n; i++)
  19. for(int j = 1; j <= n; j++)
  20. c.g[i][j] = (c.g[i][j] + g[i][k] * b.g[k][j] % p) % p;
  21. return c;
  22. }
  23. } one, ans;
  24. void init(){
  25. for(int i = 1; i <= n; i++)
  26. one.g[i][i] = 1;
  27. }
  28. matrix qpow(matrix a, int x){
  29. matrix ans = one;
  30. while(x){
  31. if(x & 1) ans = ans * a;
  32. a = a * a;
  33. x >>= 1;
  34. }
  35. return ans;
  36. }
  37. int main(){
  38. scanf("%d", &n);
  39. init();
  40. for(int i = 1; i <= n; i++)
  41. for(int j = 1; j <= n; j++)
  42. scanf("%lld", &ans.g[i][j]);
  43. scanf("%d%d%d%d", &m, &s, &f, &p);
  44. ans = qpow(ans, m);
  45. printf("%lld\n", ans.g[s][f]);
  46. return 0;
  47. }

Vijos 1194 多米诺 Domino

一个长 棋盘 (),用 的多米诺骨牌完全覆盖,问有多少种方案。

~
用二进制表示某一列每个方格被覆盖与否的情况,然后考虑用各种方式填满这一列时,下一列的情况是什么。注意:禁止在当前列竖放多米诺骨牌,因为会和这一列的另一种情况重复,但下一列可以竖放。

例如下图,中间那列是“当前列”,当前列以左已经全部填满,当前列参差不齐,当前列以右还没填。:

  1. 100 111 111
  2. 100 -> 111 or 111
  3. 110 110 111
  4. 110 110 111

将所有能转换的两种状态之间连上边(如上图 0011 可以转换为 1100 或1111),得到了一个有向图。

在这个有向图上从 1111 出发,到 1111 结束,走 n 步,方案数就是覆盖棋盘的方案数。

  1. #include <cstdio>
  2. #include <cstring>
  3. using namespace std;
  4. typedef long long ll;
  5. int n, m, p, M;
  6. const int WIDTH = 34;
  7. struct matrix {
  8. ll g[WIDTH][WIDTH];
  9. matrix(){
  10. memset(g, 0, sizeof(g));
  11. }
  12. matrix(int x){
  13. memset(g, 0, sizeof(g));
  14. for(int i = 0; i < M; i++)
  15. g[i][i] = 1;
  16. }
  17. matrix operator * (matrix b){
  18. matrix c;
  19. for(int k = 0; k < M; k++)
  20. for(int i = 0; i < M; i++)
  21. for(int j = 0; j < M; j++)
  22. c.g[i][j] = (c.g[i][j] + g[i][k] * b.g[k][j] % p) % p;
  23. return c;
  24. }
  25. } mp;
  26. matrix qpow(matrix a, int x){
  27. matrix ans(1);
  28. while(x){
  29. if(x & 1) ans = ans * a;
  30. a = a * a;
  31. x >>= 1;
  32. }
  33. return ans;
  34. }
  35. // Matrix67 的神奇位运算我找不到了 -_-|||
  36. //自己编了一个十分naive的暴力判断,也能用
  37. bool getbit(int num, int x){
  38. return num & (1 << x);
  39. }
  40. void init(){
  41. for(int i = 0; i < M; i++)
  42. for(int j = 0; j < M; j++){
  43. int ok = 1, rem = j;
  44. for(int k = 0; k < m; k++){
  45. if(!getbit(i, k)){//如果母串该位为0
  46. if(!getbit(rem, k)) ok = 0;
  47. //则子串该位必为1
  48. else rem -= (1 << k);
  49. //去掉这些横放造成的突起
  50. }
  51. }
  52. //去掉横放突起后,剩下的都是竖放的
  53. int cnt = 0;
  54. for(int k = 0; k < m; k++){
  55. if(getbit(rem, k)) cnt++;
  56. //数数竖放挨在一起的有多少
  57. if(!getbit(rem, k)){
  58. if(cnt & 1) ok = 0;
  59. cnt = 0;
  60. }
  61. }
  62. if(cnt & 1) ok = 0;
  63. if(ok) mp.g[i][j] = 1;
  64. }
  65. }
  66. int main(){
  67. scanf("%d%d%d", &n, &m, &p);
  68. M = 1 << m ;
  69. init();
  70. mp = qpow(mp, n);
  71. printf("%lld\n", mp.g[M - 1][M - 1]);
  72. return 0;
  73. }
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注