首先是一个例题,输入N和M,问用1X2的方块填满4XN的格子共有多少种方案数,答案对M取模。
分析问题可以找出所有状态,用一个1X4的位表示一行的方块情况,0表示暂时为空,1表示恰好填入方块。
对每一行进行递推,当上一行为0时,则下一行必须放入一个竖着的方块,置为1;
当上一行为1时,下一行可以为竖格子留空置0也可以横放置1。
虽然4个二进制位共有16中可能,但是合法的方案数实际只有6中,0000、0011、0110、1001、1100、1111。
可以将其编号压缩为0~5,不过16种方案也不是很多,不压缩也可以。
接下来推导公式,若上一行为0000,则下一行必须全部放入竖方块置为1111;
若上一行为1111,则下一行可以为0000、0011、1100、0110、1111等等。
以此类推出所有的状态,这一步可以通过两行枚举与一次判断做到,由于本题列数只有4,因此手推也是可以的。
令F[i][j]表示状态i能否到达状态j,能置为1,不能置为0。
可以发现F是一个NXN的矩阵(N是指状态数),令D(S)表示当前行状态为S的方案数,D'(S)为下一行状态为S的方案数。
那么D就是一个1XN的矩阵,由矩阵乘法的性质显然有 F X D = D'
如果将D初始化为第0行的状态的话,那么做几次乘法运算就能得到第几行的状态。
利用矩阵快速幂来加速乘法运算。
得到最后一行的状态D,那么最后一行全部填满的状态1111的方案数就是所求的答案。
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int maxn=1<<4; int f[maxn][maxn]; int MOD; void init(){ memset(f,0,sizeof(f)); f[0][15]=1; f[3][12]=1; f[3][15]=1; f[6][9]=1; f[6][15]=1; f[9][6]=1; f[12][3]=1; f[12][15]=1; f[15][0]=1; f[15][3]=1; f[15][6]=1; f[15][12]=1; f[15][15]=1; } int n,m; struct Matrix{ int n,m; int a[maxn][maxn]; Matrix(){} Matrix(int _n,int _m){ clear(); n=_n; m=_m; } void clear(){ n=m=0; memset(a,0,sizeof(a)); } Matrix getI(int n) const{ Matrix res(n,n); for (int i=0;i<n;i++) res.a[i][i]=1; return res; } Matrix& operator=(const Matrix& rhs){ clear(); n=rhs.n; m=rhs.m; for (int i=0;i<n;i++){ for (int j=0;j<m;j++){ a[i][j]=rhs.a[i][j]; } } return *this; } Matrix operator*(const Matrix &b) const{ Matrix tmp; tmp.clear(); tmp.n=n; tmp.m=b.m; for (int i=0;i<n;i++){ for (int k=0;k<m;k++){ for (int j=0;j<b.m;j++){ tmp.a[i][j]+=((long long)a[i][k]*b.a[k][j])%MOD; tmp.a[i][j]%=MOD; } } } return tmp; } Matrix operator^(int n) const{ Matrix ans=getI(m); Matrix A=(*this); while (n){ if (n&1) ans=ans*A; A=A*A; n>>=1; } return ans; } }; int main() { init(); while (~scanf("%d%d",&n,&m)){ if (n==0||m==0) break; MOD=m; Matrix A(maxn,maxn); Matrix F(maxn,1); for (int i=0;i<maxn;i++){ for (int j=0;j<maxn;j++){ A.a[i][j]=f[i][j]; } } F.a[(1<<4)-1][0]=1; Matrix ans=(A^n)*F; printf("%d\n",ans.a[(1<<4)-1][0]); } return 0; }