bzoj4417[SHOI2013]超级跳马

还有不到一星期就是HEOI2017了,bzoj怎么还是有人卡评测啊,不爽...以及写题解可以涨RP?为了省选攒RP中....

题意

现有一个n行m列的棋盘,一只马欲从棋盘的左上角跳到右下角.每一步它向右跳奇数列,且跳到本行或相邻行.跳跃期间,马不能离开棋盘.n<=50,m<=10^9

分析

一看这个题面和题目描述,这题就很像一个矩阵快速幂.然后可耻地想偏了...一开始想分开算行的方案数和列的方案数然后乘起来,然后发现这样需要分别统计跳1,3,5,7步(或者2,4,6,8...步,视m的奇偶性)到终点的方案数,因为走5步到达第n行和走7步到达第n行的方案数必然是不一样的.然后发现这样是构建不出矩阵的.
然后我推了一下不考虑走的步数,只要求到达第m列的方案数,发现是Fibonacci数.

为什么是Fibonacci?
记f[m]为走到第m列的方案数,考虑最后走到第m列的一步.
如果这一步大小为1,那么之前走到了第m-1列,因此给f[m]加上f[m-1].
如果这一步大小不为1,那么把最后一步的大小减2,可以得到走到第m-2列的一种方案,因此给f[m]加上f[m-2].
于是f[m]=f[m-1]+f[m-2]

然后傻叉liu_runda沿着一开始的思路,有一瞬间竟然想用走到第m列的方案数乘上走到第n行的方案数得到最终答案...这肯定是不行的,因为这些在列上走到第m列的方案走的步数是不知道的,因此走到第n行的方案数是不同的,没法算出来直接乘.

拆成两部分算方案数再乘起来的思路行不通...那么我们尝试直接计算.刚刚推的fibonacci数的结论虽然并没有什么用,但推导的过程给了我们一个启示:走到第m列的方案数只与第m-1列和第m-2列有关.一个2*50的状态数是比较少的.我们可以考虑直接定义状态f[i][j]表示走到第i列第j行的方案数,利用矩阵乘法进行转移,矩阵里保存连续两列的状态即可.
然而这么直接乘还是会有问题.
(捂脸熊.jpg)
(x,y)表示第x列第y行,一开始,有一种方案是位于(1,1).如果按照刚才建立的矩阵直接乘,我们会认为这个位于(1,1)的方案可以变成位于(3,1)的方案,因为我们假定马肯定走了一步才到达(1,1),但实际上并没有这么走一步,然后就多算了.可以发现我们把走到(3,1)的方案数多算了一个,最终答案中多算的是"假设我们走了一步到达(3,1),从(3,1)走到(m,n)的方案数",这个也可以快速幂解决.而这个快速幂就不需要考虑"从(3,1)转移到(5,1)是否会重复",因为我们要算的只是(3,1)位置多算的一个1对最终(m,n)答案的贡献,按照和一开始相同的方式计算即可(一开始重复计算的那个方案也假设我们走了一步到达(3,1)).

#include<cstdio>
#include<cstring>
const int mod=30011;
int sz;
struct matrix{
  int a[105][105];
  matrix(){
    memset(a,0,sizeof(a));
  }
  matrix(int x){
    memset(a,0,sizeof(a));
    for(int i=0;i<55;++i)a[i][i]=x;
  }
  matrix operator *(const matrix &B)const{
    matrix C;
    for(int i=1;i<=sz;++i){
      for(int j=1;j<=sz;++j){
	    for(int k=1;k<=sz;++k){
	      C.a[i][k]=(C.a[i][k]+a[i][j]*1ll*B.a[j][k]%mod)%mod;
	    }
      }
    }
    return C;
  }
}A,B,C;
matrix qpow(matrix A,int x){
  matrix ans(1);
  for(;x;x>>=1,A=A*A){
    if(x&1)ans=ans*A;
  }
  return ans;
}
int main(){
  int n,m;scanf("%d%d",&n,&m);
  sz=2*n;
  for(int i=1;i<=n;++i){
    A.a[n+i][i]=1;A.a[i][n+i]=1;A.a[n+i][n+i]=1;
  }
  for(int i=1;i<n;++i){
    A.a[n+i][n+i+1]=1;
  }
  for(int i=2;i<=n;++i){
    A.a[n+i][n+i-1]=1;
  }
  B=qpow(A,m-1);
  int ans=B.a[n+1][n+n];
  if(m>=3){
    C=qpow(A,m-3);
    ans=(ans-C.a[n+1][n+n]+mod)%mod;
  }
  printf("%d\n",ans);
  return 0;
}

posted @ 2017-04-16 21:48  liu_runda  阅读(310)  评论(0编辑  收藏  举报
偶然想到可以用这样的字体藏一点想说的话,可是并没有什么想说的. 现在有了:文化课好难