[SHOI2013] 超级跳马
题意:
现有一个n行m列的棋盘,一只马欲从棋盘的左上角跳到右下角。
每一步它向右跳奇数列,且跳到本行或相邻行,但不能离开棋盘。
求跳的方案数,对30011取模。
$n\leq 50,m\leq 10^{9}$。
题解:
挺水的一道题。设$dp_{i,j}$为走到$(i,j)$的方案数,那么$dp_{i,j}=\sum \limits_{k\%2\neq j\%2}^{k<j}{dp_{i-1,k}+dp_{i,k}+dp_{i+1,k}}$。
令$F_{i,j}=\sum \limits_{k\%2=0}^{k\leq j}{dp_{i,k}},G_{i,j}=\sum \limits_{k\%2=1}^{k\leq j}{dp_{i,k}}$,那么
- 若$j\%2=1$,则$F_{i,j}=F_{i,j-1},G_{i,j}=G_{i,j-1}+F_{i-1,j-1}+F_{i,j-1}+F_{i+1,j-1}$。
- 若$j\%2=0$,则$F_{i,j}=F_{i,j-1}+G_{i-1,j-1}+G_{i,j-1}+G_{i+1,j-1},G_{i,j}=G_{i,j-1}$。
于是直接用$2n\times 2n$的矩阵转移F和G,最后用两个前缀和相减即可得到答案。
复杂度$O(n^{3}\log{m})$。注意矩阵乘法不满足交换律,必须按顺序乘。
套路:
- 矩阵乘法优化dp:i状态能转移到j状态$\rightarrow A_{i,j}=1$。
- 形如$dp_{i,j}=\sum dp_{k,j-1}$的转移$\rightarrow$矩阵乘法优化。
- 矩阵乘法:不满足交换律,必须按顺序乘。
代码:
#include<bits/stdc++.h> #define maxn 205 #define maxm 500005 #define inf 0x7fffffff #define ll long long #define mod 30011 #define rint register ll #define debug(x) cerr<<#x<<": "<<x<<endl #define fgx cerr<<"--------------"<<endl #define dgx cerr<<"=============="<<endl using namespace std; struct Matrix{ ll x,y,M[maxn][maxn]; Matrix operator*(const Matrix b)const{ Matrix res; res.x=x,res.y=b.y; for(ll i=1;i<=x;i++) for(ll j=1;j<=b.y;j++){ res.M[i][j]=0; for(ll k=1;k<=y;k++) res.M[i][j]=(res.M[i][j]+M[i][k]*b.M[k][j]%mod)%mod; } return res; } }A,B,C; inline ll read(){ ll x=0,f=1; char c=getchar(); for(;!isdigit(c);c=getchar()) if(c=='-') f=-1; for(;isdigit(c);c=getchar()) x=x*10+c-'0'; return x*f; } inline Matrix pw(Matrix a,ll b){ Matrix res; res.x=res.y=0; while(b){ if(b&1) res=(!res.x)?a:res*a; a=a*a,b>>=1; } return res; } inline Matrix solve(int m){ if(m&1) return A*pw(C*B,m/2)*C; else return A*pw(C*B,m/2); } /*inline void print(Matrix a){ for(int i=1;i<=a.x;i++){ for(int j=1;j<=a.y;j++) cout<<a.M[i][j]<<" "; cout<<endl; } fgx; }*/ int main(){ ll n=read(),m=read(); A.x=1,A.y=2*n,B.x=B.y=C.x=C.y=2*n,A.M[1][1]=1; for(ll i=1;i<=n;i++){ B.M[i][i]=1,B.M[i+n][i+n]=1,B.M[i+n][i]=1; if(i>1) B.M[i+n-1][i]=1; if(i<n) B.M[i+n+1][i]=1; } for(ll i=1;i<=n;i++){ C.M[i][i]=1,C.M[i+n][i+n]=1,C.M[i][i+n]=1; if(i>1) C.M[i-1][i+n]=1; if(i<n) C.M[i+1][i+n]=1; } //print(A),print(B),print(C); Matrix t1=solve(m-1),t2=solve(m-2); //print(t1),print(t2); if(m%2) printf("%lld\n",(t1.M[1][n]-t2.M[1][n]+mod)%mod); else printf("%lld\n",(t1.M[1][2*n]-t2.M[1][2*n]+mod)%mod); return 0; }