地址:http://acm.bit.edu.cn/mod/programming/view.php?id=673
题意:求用1*2的砖填满m*n的矩阵的方法总数,对称的也算。
状态压缩DP。。由于接触的比较少是模仿别人AC代码写的。
首先可以简化这道题的关键之一是可以把每一行的状态记成101000(最多11位)之类的二进制表示,其中0表示该行的这一位要么是上一行的竖块占掉的,要么是这一行横放的占掉的,这两种都是不会影响下一行的情况。1表示这个位置放了一个竖块,所以会影响到下一行。那么,一旦确定了每一行的二进制表示,整个矩阵的放法也就唯一确定。(重要)。
我们设一个虚拟的第0行。那么该行的合法状态只有0一种。(即00000)每行可能的状态只有0到2^n-1(即从00..000到11....111),而根据上一行的状态可以推出下一行的合法状态,因为上一行和下一行状态合法(不冲突)需满足一定条件(见代码)。因此通过类似dfs的搜索可以从第0行推到第m行。第m行也是特殊的,其合法状态也只有0这一种。
如果第m-1行的状态和第m行的“0”不冲突,那么就返回1,否则返回0,然后层层返回回去,得到dfs(0,0)的答案。
但是这样有很多重复计算会超时,所以就用状态压缩的dp加记忆化搜索优化,即得出第k行状态为a的方法数的答案后,存入dp[a][k]中,下次就不用再递归搜索了。
参考的代码地址:http://www.cnblogs.com/goagain/archive/2012/12/05/2802772.html
我的代码:
#include<iostream>
#define ll long long
int m,n;
ll dp[(1<<11)+5][12];
bool judge(int a,int b) //判断相邻两行,状态分别为a和b,是否合法
{
bool ans=0;
if(a&b) return false; //若a与b不为0,说明两行在某相同位置均为1,不合法
int k=a|b; //k中为0的位置即是下面那一行横放产生的0,必须满足连续偶数个出现
for(int i=0;i<n;k/=2,i++) //统计所有n位连续的0
{
if(k%2==0) ans=!ans; //当前位是0, 0的奇偶计数器ans累加
else //当前位是1
{
if(ans) return false; //若连续的0奇数个,不合法
else ans=0; //否则继续计数(本行可省)
}
}
return !ans; //若最后剩下的0奇数个,不合法
}
ll dfs(int sta,int cen) //记忆化搜索,参数为当前层数的状态及当前层数(层从1开始编号)
{
if(cen==m-1) return judge(sta,0);
if(dp[sta][cen]) return dp[sta][cen];
for(int i=0;i<(1<<n);i++)
{
if(judge(sta,i)) dp[sta][cen]+=dfs(i,cen+1);
}
return dp[sta][cen];
}
int main()
{
while(scanf("%d%d",&m,&n),m|n)
{
memset(dp,0,sizeof(dp));
if(m*n%2==1) printf("0\n");
else printf("%lld\n",dfs(0,0)); //(第“0”层状态一定为0)
}
return 0;
}