题目描述
给出一个 n ∗ m n*m n∗m的矩形,然后用 1 ∗ 2 1*2 1∗2大小的多米若骨牌去填充 n ∗ m n*m n∗m的这个矩形,问有多少种填充方法。 n ∗ m < = 100 n*m<=100 n∗m<=100
题外话:从刘汝佳的算法竞赛入门经典学习来的,这里自己总结一下
轮廓线动态规划
轮廓线动态规划是状态压缩动态规划的一种,只不过不是一行一行的压,而是一条一条地像轮廓线一样的压。
这种题目可看做多段图的问题,可以根据一下的dp代码进行转换。
//设d[i][j]表示从阶段1到节点(i,j)的路径条数
now=0;
所有d[0][j]初始化为1
从小到大枚举每个要算的阶段{
now^=1;
所有d[now][j]初始化为0 //滚动数组
for 上一阶段的每个节点j
for j的每一个后继节点k
if 满足某一条件
d[now][k]+=d[1-now][j]
}
最后d[now]为最后阶段的数组
分析
这是一道轮廓线动态规划的经典题目。对于每一个
(
i
,
j
)
(i,j)
(i,j),我们考虑往左和往上放。若
(
i
,
j
)
(i,j)
(i,j)不在边界,那么能
否放仅由
(
i
−
1
,
j
)
和
(
i
,
j
−
1
)
(i-1,j)和(i,j-1)
(i−1,j)和(i,j−1)是否放了多米诺骨牌来决定的。相当于对于
(
i
,
j
)
(i,j)
(i,j)能否放牌这个阶段,仅能有两个阶段
(
i
−
1
,
j
)
(i-1,j)
(i−1,j)和
(
i
,
j
−
1
)
(i,j-1)
(i,j−1)状态决定,然而由于这两个状态不在同一行,不能同时压缩两行。所以考虑轮廓线压缩。同时这道
题符合多段图模型,所以设
d
[
i
]
[
j
]
d[i][j]
d[i][j]表示以当前格子为右下角,要不要放骨牌以及放那种骨牌。用一个数的二进制来表示轮廓线,轮廓线如下所示。
状态如下所示,图为棋盘
【 】 【 】【 】【 】【 】
【 】 【 】【 】【 】【 】
【 】 【 】【k4】【k3】【k2】
【k1】 【k0】 (i,j)
用m位二进制整数g表示
k
4
k
3
k
2
k
1
k
0
k_4k_3k_2k_1k_0
k4k3k2k1k0是否被覆盖。
考虑状态转移:
- 不放 。若 k 4 = 0 k_4=0 k4=0,则不能转移,因为棋子不可能放满。所以只有当 k 4 = 1 k_4=1 k4=1时才能转移到 k 3 k 2 k 1 11 k_3k_2k_111 k3k2k111的状态
【 】 【 】【 】【 】【 】
【 】 【 】【 】【 】【 】
【 】 【 】【1 】【k3】【k2】
【k1】 【k0】【0 】
- 往上放。只有当 k 4 k_4 k4=0 且i不是最上面一行时,才能放下,转移到 k 3 k 2 k 1 k 0 1 k_3k_2k_1k_01 k3k2k1k01的状态
【 】 【 】【 】【 】【 】
【 】 【 】【 】【 】【 】
【 】 【 】【1 】【k3】【k2】
【k1】 【k0】【1 】
- 往左放。当 k 0 = 0 k_0=0 k0=0且j不是最左列时,且 k 4 = 1 k_4=1 k4=1才能放下,转移到 k 3 k 2 k 1 11 k_3k_2k_111 k3k2k111状态。
【 】 【 】【 】【 】【 】
【 】 【 】【 】【 】【 】
【 】 【 】【1 】【k3】【k2】
【k1】 【1 】【1 】
完整代码
/*******************************
Author:galaxy yr
LANG:C++
Created Time:2019/6/24 20:50:23
*******************************/
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int maxn=15;
int n,m,now;
long long d[2][1<<maxn],ans[120][120];
int main()
{
memset(ans,-1,sizeof ans);
while(scanf("%d%d",&n,&m)==2)
{
if(n*m%2==1){printf("0\n");continue;}
if(m==0 and n==0)return 0;
if(n<m)swap(n,m);
if(~ans[n][m]){printf("%lld\n",ans[n][m]);continue;}
memset(d,0,sizeof d);
d[0][(1<<m)-1]=1;
now=0;
for(int i=0;i<n;i++)
for(int j=0;j<m;j++)
{
now^=1;
memset(d[now],0,sizeof d[now]);
for(int k=0;k<(1<<m);k++)
{
if(k&(1<<m-1)) d[now][(k<<1)^(1<<m)]+=d[1-now][k];
if(i and !(k&(1<<m-1)))d[now][(k<<1)^1]+=d[1-now][k];
if(j and (k&(1<<m-1)) and !(k&1))d[now][(k<<1)^(1<<m)|(1<<1)^1]+=d[1-now][k];
}
}
printf("%lld\n",d[now][(1<<m)-1]);
ans[n][m]=d[now][(1<<m)-1];
}
return 0;
}