@RabbitHu
2017-08-03T01:48:55.000000Z
字数 4332
阅读 2458
笔记
有一个编号为 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 竖排排列的一个原始矩阵,得到的就是最终结果。
#include <cstdio>#include <cmath>#include <cstring>#include <algorithm>#include <iostream>using namespace std;const int MAXN = 105;int n, m, k;struct matrix {int g[MAXN][MAXN];matrix(){memset(g, 0, sizeof(g));}matrix operator * (matrix b) {matrix c;for(int k = 1; k <= n; k++)for(int i = 1; i <= n; i++)for(int j = 1; j <= n; j++)c.g[i][j] += g[i][k] * b.g[k][j];return c;}} one, op, tp, rest, ans;void init(){for(int i = 1; i <= n; i++)one.g[i][i] = 1;op = rest = one;for(int i = 1; i <= n; i++)ans.g[i][1] = i;}matrix qpow(matrix a, int x){if(x == 0) return one;matrix t = qpow(a, x >> 1);if(x & 1) return t * t * a;return t * t;}int main(){scanf("%d%d%d", &n, &m, &k);init();for(int i = 1; i <= m; i++){memset(tp.g, 0, sizeof(tp.g));int v;for(int j = 1; j <= n; j++){scanf("%d", &v);tp.g[j][v] = 1;}op = tp * op;if(i <= k % m)rest = tp * rest;}op = qpow(op, k / m);ans = rest * op * ans;for(int i = 1; i <= n; i++){if(i > 1) printf(" ");printf("%d", ans.g[i][1]);}printf("\n");return 0;}
@ 矩阵乘法注意顺序。
有一排格子,一开始在第 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,最后一行为递推式中对应的系数。
#include <cstdio>#include <cmath>#include <cstring>#include <algorithm>#include <iostream>using namespace std;typedef long long ll;const int MO = 7777777, MAXN = 12;int n, m; //n是技能等级, m是监狱个数struct matrix {ll g[MAXN][MAXN];matrix(){memset(g, 0, sizeof(g));}matrix operator * (matrix b) {matrix c;for(int k = 1; k <= n; k++)for(int i = 1; i <= n; i++)for(int j = 1; j <= n; j++)c.g[i][j] = (c.g[i][j] + g[i][k] * b.g[k][j] % MO) % MO;return c;}} op, ans, one;matrix qpow(matrix a, int x){if(x == 0) return one;matrix t = qpow(a, x >> 1);if(x & 1) return t * t * a;return t * t;}void init(){for(int i = 1; i <= n; i++)one.g[i][i] = 1;for(int i = 1; i < n; i++)op.g[i][i + 1] = 1;for(int i = 1; i <= n; i++)op.g[n][i] = 1;ans.g[n][1] = 1;}int main(){scanf("%d%d", &n, &m);init();op = qpow(op, m);ans = op * ans;printf("%lld\n", ans.g[n][1]);return 0;}
水题:有向图,求两点之间恰好走k条边能到达的路径数目。
~
一个结论:k条边路径数目 = 邻接矩阵的k次方中对应的路径数目
#include <cstdio>#include <cmath>#include <cstring>#include <algorithm>#include <iostream>using namespace std;typedef long long ll;const int MAXN = 55;int n, s, f, m, p;struct matrix {ll g[MAXN][MAXN];matrix(){memset(g, 0, sizeof(g));}matrix operator * (matrix b) {matrix c;for(int k = 1; k <= n; k++)for(int i = 1; i <= n; i++)for(int j = 1; j <= n; j++)c.g[i][j] = (c.g[i][j] + g[i][k] * b.g[k][j] % p) % p;return c;}} one, ans;void init(){for(int i = 1; i <= n; i++)one.g[i][i] = 1;}matrix qpow(matrix a, int x){matrix ans = one;while(x){if(x & 1) ans = ans * a;a = a * a;x >>= 1;}return ans;}int main(){scanf("%d", &n);init();for(int i = 1; i <= n; i++)for(int j = 1; j <= n; j++)scanf("%lld", &ans.g[i][j]);scanf("%d%d%d%d", &m, &s, &f, &p);ans = qpow(ans, m);printf("%lld\n", ans.g[s][f]);return 0;}
一个长 高 棋盘 (),用 的多米诺骨牌完全覆盖,问有多少种方案。
~
用二进制表示某一列每个方格被覆盖与否的情况,然后考虑用各种方式填满这一列时,下一列的情况是什么。注意:禁止在当前列竖放多米诺骨牌,因为会和这一列的另一种情况重复,但下一列可以竖放。
例如下图,中间那列是“当前列”,当前列以左已经全部填满,当前列参差不齐,当前列以右还没填。:
100 111 111100 -> 111 or 111110 110 111110 110 111
将所有能转换的两种状态之间连上边(如上图 0011 可以转换为 1100 或1111),得到了一个有向图。
在这个有向图上从 1111 出发,到 1111 结束,走 n 步,方案数就是覆盖棋盘的方案数。
#include <cstdio>#include <cstring>using namespace std;typedef long long ll;int n, m, p, M;const int WIDTH = 34;struct matrix {ll g[WIDTH][WIDTH];matrix(){memset(g, 0, sizeof(g));}matrix(int x){memset(g, 0, sizeof(g));for(int i = 0; i < M; i++)g[i][i] = 1;}matrix operator * (matrix b){matrix c;for(int k = 0; k < M; k++)for(int i = 0; i < M; i++)for(int j = 0; j < M; j++)c.g[i][j] = (c.g[i][j] + g[i][k] * b.g[k][j] % p) % p;return c;}} mp;matrix qpow(matrix a, int x){matrix ans(1);while(x){if(x & 1) ans = ans * a;a = a * a;x >>= 1;}return ans;}// Matrix67 的神奇位运算我找不到了 -_-|||//自己编了一个十分naive的暴力判断,也能用bool getbit(int num, int x){return num & (1 << x);}void init(){for(int i = 0; i < M; i++)for(int j = 0; j < M; j++){int ok = 1, rem = j;for(int k = 0; k < m; k++){if(!getbit(i, k)){//如果母串该位为0if(!getbit(rem, k)) ok = 0;//则子串该位必为1else rem -= (1 << k);//去掉这些横放造成的突起}}//去掉横放突起后,剩下的都是竖放的int cnt = 0;for(int k = 0; k < m; k++){if(getbit(rem, k)) cnt++;//数数竖放挨在一起的有多少if(!getbit(rem, k)){if(cnt & 1) ok = 0;cnt = 0;}}if(cnt & 1) ok = 0;if(ok) mp.g[i][j] = 1;}}int main(){scanf("%d%d%d", &n, &m, &p);M = 1 << m ;init();mp = qpow(mp, n);printf("%lld\n", mp.g[M - 1][M - 1]);return 0;}