【BZOJ4417】[Shoi2013]超级跳马
Description
现有一个n行m列的棋盘,一只马欲从棋盘的左上角跳到右下角。每一步它向右跳奇数列,且跳到本行或相邻行。跳越期间,马不能离开棋盘。例如,当n = 3, m = 10时,下图是一种可行的跳法。
试求跳法种数mod 30011。
Input
仅有一行,包含两个正整数n, m,表示棋盘的规模。
Output
仅有一行,包含一个整数,即跳法种数mod 30011。
Sample Input
3 5
Sample Output
10
HINT
对于100%的数据,1 ≤ n ≤ 50,2 ≤ m ≤ 10^9
题解:一开始搞了个200*200的矩阵,后来又搞了一个150*150的矩阵,最后发现100*100好像也行。。。不过不管了。
用f[i][j]表示走到第i列,第j行的方案数,那么我们对于所有i为奇数的f维护前缀和s1,再对所有i为偶数的f维护前缀和s0,就可以转移了。
不过。。。如果用分块矩阵乘法的话,我们要将f,s0,s1三个矩阵拼起来,一会从s0转移到f,一会从s1转移到f,怎么办呢?维护两个转移矩阵就好了嘛!我们设x表示从s0或s1到i的转移矩阵,具体地转移是这样的:
令 $A=egin{pmatrix}\ & I &\ X & X & Iend{pmatrix}$,$B=egin{pmatrix}\ X & I & X\ & & Iend{pmatrix}$,我们的ans矩阵就是ans*A*B*A*B...,我们令C=A*B,所以就变成了$ans*C^{m>>1}$,最后可能再乘一个A。
P.S:同学提供了一个更巧妙的做法,令方程为f[i][j]=f[i-2][j]+f[i-1][j-1]+f[i-1][j]+f[i-1][j+1],然后维护个100*100的矩阵就行了。
#include <cstdio> #include <cstring> #include <iostream> using namespace std; const int P=30011; int n,m; struct M { int a[160][160]; int * operator [] (int b) {return a[b];} M () {memset(a,0,sizeof(a));} M operator * (M b) { M c; int i,j,k; for(i=1;i<=3*n;i++) for(j=1;j<=3*n;j++) for(k=1;k<=3*n;k++) c[i][j]=(c[i][j]+a[i][k]*b[k][j])%P; return c; } }; M ans,A,B,C; void pm(int y) { while(y) { if(y&1) ans=ans*C; C=C*C,y>>=1; } } int main() { scanf("%d%d",&n,&m),m--; int i; ans[1][1]=ans[1][2*n+1]=1; for(i=1;i<=n;i++) { if(i>1) A[2*n+i][i-1]=A[2*n+i][n+i-1]=B[n+i][i-1]=B[n+i][2*n+i-1]=1; A[2*n+i][i]=A[2*n+i][n+i]=A[2*n+i][2*n+i]=A[n+i][n+i]=B[n+i][i]=B[n+i][n+i]=B[n+i][2*n+i]=B[2*n+i][2*n+i]=1; if(i<n) A[2*n+i][i+1]=A[2*n+i][n+i+1]=B[n+i][i+1]=B[n+i][2*n+i+1]=1; } C=A*B; pm(m>>1); if(m&1) ans=ans*A; printf("%d",ans[1][n]); return 0; }