题解 P3990 [SHOI2013]超级跳马

$\texttt{Link}$

显然有 DP 做法,$f(i,j)$ 表示走到第 $i$ 行第 $j$ 列的方案数量。

有转移方程$$f(i,j)=f(i-1,j-1)+f(i,j-1)+f(i+1,j-1)+\sum\limits_{i=1}^{\left\lfloor{\frac{j}{2}}\right\rfloor}f(i-1,j-1-2i)+f(i,j-1-2i)+f(i+1,j-1-2i)$$

这个转移方程转移复杂度太高,发现后面那个式子即为 $f(i,j-2)$。

所以有更简洁的转移方程$$f(i,j)=f(i-1,j-1)+f(i,j-1)+f(i+1,j-1)+f(i,j-2)$$

然后发现,数据范围 $n\le50,m\le10^9$,暴力转移 $O(nm)$,过高。

考虑用矩阵优化。

矩阵中有上两列每行的状态。如 $n=3$ 时有转移矩阵

$\begin{bmatrix}1&1&0&1&0&0\\1&1&1&0&1&0\\0&1&1&0&0&1\\1&0&0&0&0&0\\0&1&0&0&0&0\\0&0&1&0&0&0\end{bmatrix}\times\begin{bmatrix}f(1,i-1)\\f(2,i-1)\\f(3,i-1)\\f(1,i-2)\\f(2,i-2)\\f(3,i-2)\end{bmatrix}=\begin{bmatrix}f(1,i)\\f(2,i)\\f(3,i)\\f(1,i-1)\\f(2,i-1)\\f(3,i-1)\end{bmatrix}$

这个矩阵的构造,以 $f(2,i)$ 为例$$f(2,i)=1\times f(1,i-1)+1\times f(2,i-1)+1\times f(3,i-1)+0\times f(1,i-2)+1\times f(2,i-2)+0\times f(3,i-2)$$

其对应的系数即为初始矩阵的第 $2$ 行。

矩阵快速幂即可。

因为最初第一列不需要转移,所以将 $m\gets m-1$,并特判一下 $m=2$ 的情况,然后会发现,在 计算 $f(1,3)$ 时,会有从 $f(1,1)$ 来的转移,然而 $f(1,1)$ 并没有在其之前列的转移,导致多算了一次。所以到 $f(n,m)$ 时,多出的方案数,即为从 $(1,1)$ 到 $(n,m-2)$ 的方案数,即多出 $f(n,m-2)$,减去它即可。

#include <bits/stdc++.h>
using namespace std;
const int N = 1e2 + 10, P = 30011;
int n, m;
struct Matrix {
    int a[N][N];
    Matrix() { memset(a, 0, sizeof(a)); }
    void init() {
        for(int i = 1; i <= (n << 1); i++) a[i][i] = 1;
    }
    friend Matrix operator * (Matrix a, Matrix b) {
        Matrix res;
        for(int i = 1; i <= (n << 1); i++)
            for(int j = 1; j <= (n << 1); j++)
                for(int k = 1; k <= (n << 1); k++)
                    res.a[i][j] = (res.a[i][j] + a.a[i][k] * b.a[k][j]) % P;
        return res;      
    }
} base;
Matrix qpow(Matrix a, int k) {
    Matrix res; res.init();
    for(; k; a = a * a, k >>= 1)
        if(k & 1) res = res * a;
    return res;
}
void build() {
    for(int i = 1; i <= n; i++)
        for(int j = max(i - 1, 1); j <= min(i + 1, n); j++)
            base.a[i][j] = 1;
    for(int i = 1; i <= n; i++)
        base.a[i][i + n] = base.a[i + n][i] = 1;
}
int main() {
    scanf("%d%d", &n, &m);
    if(m == 2) {
        if(n <= 2) printf("1");
        else printf("0");
        return 0;
    }
    build();
    m -= 2;//因为最后有一次单独算,所以m多减1
    Matrix ans = qpow(base, m);
    int s = ans.a[1][n << 1];
    ans = ans * base;
    int t = ans.a[1][n];
    printf("%d", (t - s + P) % P);
    return 0;
}
posted @ 2021-07-23 15:50  Terac  阅读(15)  评论(0)    收藏  举报  来源