给出一个n*m的矩形,然后用1*2大小的多米若骨牌去填充n*m的这个矩形,问有多少种填充方法。
分析:典型的轮廓线动态规划题目。详见刘汝佳新书:算法竞赛入门经典:训练指南P384.
首先本题目是以一个一个的格子为基础来计算状态的,即每次都是考虑当前位置的格子如何放左上骨牌(以当前位置为最右下角,即只不放,左放,和上放3种情况,没有右放和下放)。且本题的状态都是一条一条的轮廓线。如图:
1 | 1 | 1 | 1 | 1 |
1 | 1 | K4 | K3 | K2 |
K1 | K0 | O |
|
|
|
|
|
|
|
令<K3K2K1K0O> 为这5个数字对应的二进制值的十进制结果。K3K2K1K0O=11110表示由这5个格构成的轮廓线状态 如
K4 K3 K2 K1 K0 O 分别为: 0 1 1 1 1 0 时,
<K3K2K1K0O> = 30,<K4K3K2K1K0> = 15
本题的状态为d[cur][<K3K2K1K0O>]表示 当前以O格为末尾的轮廓线状态为<K3K2K1K0O>的值对应二进制表示时且K3之前的所有格子的值都为1时构成上图所给状态的方法总数。其中 当前轮廓线为K3K2K1K0O,前一个轮廓线为K4K3K2K1K0。当前的轮廓线状态数用d[cur][ <K3K2K1K0O>]表示,前一个轮廓线状态数用d[1-cur][ <K4K3K2K1K0>]表示。
现在初始时 K4K3K2K1K0O的值对应为 011110.
O格的每一种操作(不放,左方,上方)都会联系前后两种不同的轮廓线。
假设在O格放上骨牌,K4和O值都变为1,则d[cur][<K3K2K1K0O>] =d[cur][<11111>] +=d[1-cur][<K4K3K2K1K0>]=d[1-cur][<01111>] 这句话的意义是 在O格放上骨牌,则以O格为末尾的轮廓线为11111,且已放的格子情况为:
1 | 1 | 1 | 1 | 1 |
1 | 1 | 1 | 1 | 1 |
1 | 1 | 1 |
|
|
|
|
|
|
|
的时候有d[1-cur][<01111>](这是一个整数值)这么多种情况是通过在轮廓线<K4K3K2K1K0>=<01111>的基础上,放O格上骨牌而产生来的。
假设在O格不放,K4的值不改变依然为0,如果执行:d[cur][<K3K2K1K0O>] =d[cur][<11110>] += d[1-cur][<K4K3K2K1K0>]=d[1-cur][<01111>] 这样是非法的。因为不放O格,K4的值永远为0(K4的值如果为0只能通过O格放上骨牌来置1,O格之后的格子无论怎么放骨牌都不会改变K4的值),所以如果执行上述非法操作,违反了关于d[cur][<K3K2K1K0O>]的定义(要求K3之前所有格子的值都为1).
假设在O格放左骨牌,也是非法操作,因为O格左边已经是1了,不可能放的下左骨牌。
且在首行不能上放,在首列不能左放。
最后当循环处理到第(n,m)格时,轮廓线为<11111>时表示轮廓线前面的n-1行也全是11111,所以最终结果为d[cur][<11111>]。
并且初始值为d[0][<11111>]=1,其他d[0][其他值]=0(其实这个可以不弄,因为首行不能上放,所以首行轮廓线状态数不可能会引用到d[0][01111]这样的初值。)。
AC代码:
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn = 15;
long long d[2][1<<maxn];
int n,m,cur;
void update(int a,int b)//a是包含m位1进制数的老状态,b是包含m+1位1进制数的新状态
{
if(b&(1<<m)) d[cur][b^(1<<m)] += d[1-cur][a];//只有新轮廓线首位为1时才更新
}
int main()
{
while( scanf("%d%d",&n,&m)==2&&n&&m )
{
if(m>n)swap(n,m);
memset(d,0,sizeof(d));
cur=0;
d[cur][(1<<m)-1]=1;
for(int i=0;i<n;i++)
for(int j=0;j<m;j++)
{
cur ^=1;
memset(d[cur],0,sizeof(d[cur]));
for(int k=0;k<(1<<m);k++)//k的二进制形式表示前一个格子的轮廓线状态
{
update(k,k<<1);//当前格不放,直接k左移一位就表示带m+1位的新轮廓线的状态
if(i && !(k&(1<<(m-1)) )) update(k,(k<<1)^(1<<m)^1);//上放,要求轮廓线首为0
if(j && (!(k&1)) ) update(k,(k<<1)^3);//左放,要求轮廓线尾0,首1
}
}
printf("%I64d\n",d[cur][(1<<m)-1]);
}
return 0;
}