题目链接
http://poj.org/problem?id=3420
题目大意
给你一个 4∗n 大小的棋盘,要你在上面用若干个 1∗2 大小的方块填满,两个方块之间互相不能重叠。问填满方块的方案数
思路
考虑 n 比较小的情况。
用
DP方程为
这个
S
之间的转移,我们可以通过手工人肉出来,也很容易发现其中的规律
图片来自http://www.hankcs.com/program/algorithm/poj-3420-quad-tiling.html
可以发现,第
然后发现题目的
n
非常大,上面的DP可以把二进制状态看成是有向图里的结点,转移看成是有向边,这就变成了一个很经典的线性代数问题:走
代码
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#define MAXN 110
using namespace std;
typedef long long int LL;
LL MOD;
struct Matrix
{
int n,m;
LL num[MAXN][MAXN];
Matrix()
{
n=m=0;
memset(num,0,sizeof(num));
}
}B,A,mp,one;
Matrix operator*(Matrix a,Matrix b)
{
Matrix ans;
ans.n=a.n,ans.m=b.m;
for(int k=1;k<=a.m;k++)
for(int i=1;i<=ans.n;i++)
for(int j=1;j<=ans.m;j++)
ans.num[i][j]=(ans.num[i][j]+(a.num[i][k]*b.num[k][j])%MOD)%MOD;
return ans;
}
Matrix fastPow(Matrix base,int pow)
{
Matrix ans=one;
while(pow)
{
if(pow&1) ans=ans*base;
base=base*base;
pow>>=1;
}
return ans;
}
int n;
int main()
{
for(int S=0;S<16;S++)
{
int newS=0;
for(int i=0;i<4;i++)
{
if(S&(1<<i)) continue;
newS|=1<<i;
}
mp.num[S+1][newS+1]++;
}
mp.num[13][1]++;
mp.num[10][1]++;
mp.num[4][1]++;
mp.num[1][4]++;
mp.num[1][10]++;
mp.num[1][13]++;
mp.num[1][1]++;
mp.num[2][9]++;
mp.num[2][3]++;
mp.num[9][2]++;
mp.num[3][2]++;
one.n=one.m=mp.n=mp.m=16;
B.n=1,B.m=16;
B.num[1][1]=1;
for(int i=1;i<=16;i++)
one.num[i][i]=1;
while(scanf("%d%lld",&n,&MOD)!=EOF&&!(!n&&!MOD))
{
B.n=1,B.m=16;
B.num[1][16]=1;
B=fastPow(mp,n);
/*for(int i=1;i<=16;i++)
{
for(int j=1;j<=16;j++)
printf("%lld ",mp.num[i][j]);
printf("\n");
}*/
printf("%lld\n",B.num[1][1]);
}
return 0;
}