[SHOI 2013]超级跳马

Description

题库连接

现有一个 $n$ 行 $m$ 列的棋盘,一只马欲从棋盘的左上角跳到右下角。每一步它向右跳奇数列,且跳到本行或相邻行。跳越期间,马不能离开棋盘。试求跳法种数,取模。

$1\leq n\leq50,2\leq m\leq 10^9$

Solution

令 $f_{i,j}$ 表示在第 $i$ 列第 $j$ 行的方案数。那么,$f_{i,j}=\sum\limits_{k=1}^i\text{odd}(i-k)\cdot(f_{k,j-1}+f_{k,j}+f_{k,j+1})$。

显然如果我们分奇偶做一个前缀和的话就可以 $O(1)$ 转移。

由于 $n$ 比较大,并且决策(转移)比较少,那么我们考虑用矩乘法优化。构造 $1\times n$ 的状态矩阵 $\mathbf{A_n}$,第 $1$ 行第 $i$ 列表示从左上角到 $(n,i)$ 有多少种方法。

显然 $\mathbf{A_n}[1][1]=1$。构造状态矩阵 $\mathbf{S}=\begin{bmatrix}\mathbf{A_n} &\mathbf{A_{n-1}}\end{bmatrix}$。

再构造转移矩阵 $\mathbf{T}=\begin{bmatrix}\mathbf{V} &\mathbf{I}\\mathbf{I} &0\end{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\n", ((S*B).a[1][n>>1]-S.a[1][n]+yzh)%yzh);
    return 0;
}
posted @ 2020-01-30 22:03  NaVi_Awson  阅读(201)  评论(1编辑  收藏  举报