人生第一道状态压缩DP,我这个弱渣想了N天终于弄懂了◑﹏◐
题目大意:求1*2的地板填满n*m的砖块有多少种不同的方法
思路:位运算+DFS+状态压缩。
当高度和宽度都为奇数时答案为0。
对于每一个位置,我们有三种放置方法:
1. 竖直放置
2. 水平放置
3. 不放置
d为当前列号 ,初始化d, now, pre都为0;对应以上三种放置方法,now, pre的调整为:
1. d = d + 1, now << 1 | 1, pre << 1;
2. d = d + 2, now << 2 | 3, pre << 2 | 3;
3. d = d + 1, now << 1, pre << 1 | 1;
先就第一种情况解释一下,另外的两种情况可以类推
now<<1|1即为把s1的二进制表示后面加上一个1,对就于本题来说就是(d+1)列上放
置,pre<<1即为把s2的二进制表示后面加上一个0,对于本题来说就是(d+1)列上不放置。
now对应于本行的状态,pre对应于上一行的状态,能竖直放置意味着上一行的(d+1)列是空着的,因此此时上一行的状态为pre<<1,同时竖
置放置了之后,则本行(d+1)行放置了东西,状态于是变为now<<1|1;
当d = w时保存状态
对于初始时的f值,可以假设第0行全满,第一行只有两种放法:
1. 水平放置 d = d + 2, s << 2 | 3;
2. 不放置 d = d + 1, s << 1;
最后只要判断h行是否全为1就行了,只取最后一行全为1的答案,也就是(1<
#include <iostream>
#include <string>
#include <cstring>
#include <stdio.h>
#include <cstdlib>
#include <map>
#include <set>
#include <vector>
#include <queue>
#include <functional>
#include <algorithm>
#include <cmath>
#include <assert.h>
#include <stack>
using namespace std;
#define maxn 1<<12
int h,w,p,q;
long long dp[2][maxn];
void dfs(int col,int now,int pre) //now当前行状态,pre前一行状态
{
if(col==w) //col已经移到当前行最左边一列
{
dp[q][now]+=dp[p][pre];
return;
}
if(col+1<=w)
{
dfs(col+1,now<<1,pre<<1|1); //不放 ,故而pre<<1|1,即将当前行的下一列由0变1
//当前行在下一个dfs变成前一行,即pre
dfs(col+1,now<<1|1,pre<<1); //竖放
//当下一行行的下一列由0变1(now<<1|1)
}
if(col+2<=w)
dfs(col+2,now<<2|3,pre<<2|3); //横放
//3二进制为100,或运算将当前行的下一行同一列由0变成1
}
void solve()
{
int i;
if((( w * h ) & 1) ) //&1相当于%2,但是与运算更快
{
printf("0\n");
return;
}
memset(dp,0,sizeof(dp));
p=0;
dp[0][(1<<w)-1]=1;
for(i=1;i<=h;i++)
{
q=p^1; //滚动数组,p,q轮替交换,p是上一行,q是当前行
dfs(0,0,0);
memset(dp[p],0,sizeof(dp[0]));
p=q;
}
printf("%lld\n",dp[q][(1<<w)-1]);
}
int main()
{
while(~scanf("%d%d",&h,&w)&&(w||h))
{
if(w>h)swap(w,h);
solve();
}
return 0;
}