【BZOJ4417】[Shoi2013]超级跳马 矩阵乘法

【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;
}
原文地址:https://www.cnblogs.com/CQzhangyu/p/7561492.html