4417: [Shoi2013]超级跳马
Time Limit: 10 Sec Memory Limit: 256 MB
Submit: 379 Solved: 230
[Submit][Status][Discuss]Description
现有一个n行m列的棋盘,一只马欲从棋盘的左上角跳到右下角。每一步它向右跳奇数列,且跳到本行或相邻行。跳越期间,马不能离开棋盘。例如,当n = 3, m = 10时,下图是一种可行的跳法。试求跳法种数mod 30011。Input
仅有一行,包含两个正整数n, m,表示棋盘的规模。Output
仅有一行,包含一个整数,即跳法种数mod 30011。Sample Input
3 5Sample Output
10HINT
对于100%的数据,1 ≤ n ≤ 50,2 ≤ m ≤ 10^9
题解
首先我们发现由于某个点的状态可以从与它所在列的编号的奇偶性不同的所有列转移, 所以这应该是一个前缀和.
而第 $i$ 列的前缀和可以从前一列转移, 但奇数列与偶数列所转移的位置并不同, 所以转移过程中需要记录两个参考向量. 这样的话转移过程中的向量维数就是 $2n$ , 我们就需要一个 $2n\times 2n$ 的矩阵了. 我的转移矩阵大概长这样:
其中左上部分用于统计答案, 左下部分把奇偶性相同的列也加和起来, 右上部分用于把上一列答案下推一列.
然后最后一轮的时候要把左下和右上部分置零(其实主要是左下部分置零, 因为奇偶性相同的列不能再加入答案了)
A掉之后整个人都赛艇了2333333
参考代码
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm> const int MOD=; int n,m; struct Matrix{
int n;
int m[][];
Matrix(int n=){
this->n=n;
memset(m,,sizeof(m));
}
}; struct Vector{
int n;
int v[];
Vector(int n=){
this->n=n;
memset(v,,sizeof(v));
}
}; Vector operator* (const Vector& v,const Matrix& m){
Vector ans(v.n);
for(int i=;i<=v.n;i++)
for(int j=;j<=v.n;j++)
(ans.v[j]+=1ll*v.v[i]*m.m[i][j])%=MOD;
return ans;
} Matrix operator* (const Matrix& a,const Matrix& b){
Matrix ans(a.n);
for(int i=;i<=a.n;i++)
for(int j=;j<=a.n;j++)
for(int k=;k<=a.n;k++)
(ans.m[i][j]+=1ll*a.m[i][k]*b.m[k][j])%=MOD;
return ans;
} int main(){
scanf("%d%d",&n,&m);
Vector v(*n);
Matrix mx(*n);
v.v[]=;
for(int i=;i<=n;i++){
mx.m[i+n][i]=;
mx.m[i][i+n]=;
for(int j=std::max(,i-);j<=std::min(n,i+);j++){
mx.m[i][j]=;
}
}
m-=;
while(m>){
if((m&)!=){
v=v*mx;
}
mx=mx*mx;
m>>=;
}
memset(mx.m,,sizeof(mx.m));
for(int i=;i<=n;i++){
for(int j=std::max(,i-);j<=std::min(n,i+);j++){
mx.m[i][j]=;
}
}
if((m&)==)
v=v*mx;
printf("%d\n",v.v[n]);
return ;
}
Backup