[题解]UVA11270 Tiling Dominoes
看到了很多dalao发的题解,但是理解得不是特别清楚,研究了一下午终于懂了(:D)
这里我们逐格进行DP。但记录的状态是各行内的轮廓线上格子的覆盖情况。
- 如果当前格上方为0,那么必须放一个竖放的骨牌
为什么呢?因为上方白色格子的内容是已经确定的,也就是说,此时不放一块向上的骨牌,那么上方这个格子就会永远无法被覆盖,这与题目要求的覆盖整个网格不符。因此必须放一个竖向骨牌
- 那么当前格上方为1,那么你肯定无法放一个向上的骨牌。
综上,若上方为0则放置竖向骨牌(!(k&p[j]))
同时,当前格也不能位于第一行。(j>1)
接下来我们来整个转移方程出来。
上图
在经过转移后,我们的动规推进了一格,k的第j位在推进前后,从当前格的上一个格子,变成了当前格本身(观察左下角的格子排列)(理解这一点很重要(至少对于我自己来说))
即k的第j位 从0变成了1,而原来的上方格子则不用再考虑了
或许你可以停下来思考一下……
由于求的是总方案数,因此转移时采用加和,初始化为0。
因此,状态转移方程如下:
2.横放:
现在有了第一种的经验,我们可以依样画葫芦,轻松地推出其他的情况
试着自己推一推?
情况1中我们已经得知,如果上方的格子未被覆盖,则必定要放一个竖向骨牌将其填满(重点2处)。即下文条件2.
横放的条件是
- 左侧格子没有被覆盖(!(k&q[j-1]))
- 上方的格子已经被覆盖(k&q[j])
- 不在第一列(j>1)
而具体的转移方法 则是
横放后
状态转移方程:
3.留空:
同情况2条件2,只有在上方格子被覆盖的情况下,才可以进行除了竖放以外的操作,而对于留空,则没有什么要求。
则条件是:
- 上方的格子已经被覆盖(k&q[j])
转移方法则是
思考 留空 与 竖放 的关系
可以结合下面这张图来理解。
初始化为f[0][(1<<m)-1]=1; (把第0行填满)
最终答案为f[d][(1<<m)-1]; (最后一行填满)
一些优化
-
由于每一行的状态仅有上一行推出,且最终只取最后一行,可以使用2行的滚动数组优化;
-
预处理p数组(即1<<(i-1));
-
防止状态压缩超出整数范围,始终保证n>m,使m≤10;
代码
#include<bits/stdc++.h>
using namespace std;
int n,m;
long long int f[2][(1<<10)];
int p[20];
int main(){
for(int i=1;i<=15;i++){
p[i]=1<<(i-1);
}//预处理
while(~scanf("%d %d",&n,&m))
{
if(n<m) swap(n,m);//交换,使m不大于10
int d=0;//滚动数组下标
memset(f,0,sizeof(f));//清空
f[0][(1<<m)-1]=1;//初始化
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
d^=1;//d在0与1之间滚动
memset(f[d],0,sizeof(f[d]));//清空
for(int k=0;k<(1<<m);k++){
if(k&p[j])
f[d][k^p[j]]+=f[d^1][k];//留空
if((j>1) && !(k&p[j-1]) && (k&p[j]))//横放
f[d][k|p[j-1]]+=f[d^1][k];
if((i>1) && !(k&p[j]))
f[d][k|p[j]]+=f[d^1][k];//竖放
}
}
}
cout<<f[d][(1<<m)-1]<<endl;//最终输出最后一行也填满的情况
}
}