Description
现有一个 (n) 行 (m) 列的棋盘,一只马欲从棋盘的左上角跳到右下角。每一步它向右跳奇数列,且跳到本行或相邻行。跳越期间,马不能离开棋盘。试求跳法种数,取模。
(1leq nleq50,2leq mleq 10^9)
Solution
令 (f_{i,j}) 表示在第 (i) 列第 (j) 行的方案数。那么,(f_{i,j}=sumlimits_{k=1}^i ext{odd}(i-k)cdot(f_{k,j-1}+f_{k,j}+f_{k,j+1}))。
显然如果我们分奇偶做一个前缀和的话就可以 (O(1)) 转移。
由于 (n) 比较大,并且决策(转移)比较少,那么我们考虑用矩乘法优化。构造 (1 imes n) 的状态矩阵 (mathbf{A_n}),第 (1) 行第 (i) 列表示从左上角到 ((n,i)) 有多少种方法。
显然 (mathbf{A_n}[1][1]=1)。构造状态矩阵 (mathbf{S}=egin{bmatrix}mathbf{A_n} &mathbf{A_{n-1}}end{bmatrix})。
再构造转移矩阵 (mathbf{T}=egin{bmatrix}mathbf{V} &mathbf{I}\mathbf{I} &0end{bmatrix})。其中 (mathbf V) 是转移矩阵,用来记录 DP 的转移方程。右上角的单位矩阵是用来累加前缀和到下一个状态矩阵上的。左下角的单位矩阵是将当前的 (mathbf{A_n}) 变成下一个状态矩阵的 (mathbf{A_{n-1}})。
于是矩阵优化即可。注意由于做的是前缀和,所以最后输出时要做一次差分来算出最后一列的方案数。
Code
#include <bits/stdc++.h>
using namespace std;
const int N = 105, yzh = 30011;
int n, m;
struct mat {
int a[N][N];
mat () {memset(a, 0, sizeof(a)); }
mat operator * (const mat &b) const {
mat ans;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
for (int k = 1; k <= n; k++)
(ans.a[i][j] += a[i][k]*b.a[k][j]%yzh) %= yzh;
return ans;
}
} S, T, B;
void qpow(int b) {
while (b) {
if (b&1) S = S*T;
b >>= 1, T = T*T;
}
}
int main() {
scanf("%d%d", &n, &m);
S.a[1][1] = 1;
for (int i = 1; i <= n; i++)
T.a[i][i+n] = T.a[i+n][i] = T.a[i][i] = 1;
for (int i = 1; i < n; i++)
T.a[i+1][i] = T.a[i][i+1] = 1;
B = T; n <<= 1;
qpow(m-2);
printf("%d
", ((S*B).a[1][n>>1]-S.a[1][n]+yzh)%yzh);
return 0;
}