Description
Input
Output
Sample Input
1 2 1 3 1 4 2 2 2 3 2 4 2 11 4 11 0 0
Sample Output
1 0 1 2 3 5 144 51205
今天晚上状态超级不好啊==本文参考:点击打开链接
题意:用1x2的方块密铺nxm的矩形,问方法数,一看题就想到了hdu2946骨牌铺方格,只不过那个题是只有两行,其实只有两行就足以找出规律:
1.假设已知Fibo(n),如果将第n+1块板竖着插入排头,会发现并没有多出组合,Fibo(n+1)=Fibo(n),插入其他位置的情况下, 如果第n+1个位置是竖板,等同于前面的情况。 2.只有在n和n+1的位置是横板,才能多出组合,多的组合数量为Fibo(n-1); 3.推出 Fibo(n+1)=Fibo(n)+Fibo(n-1);即为Fibo(n)=Fibo(n-1)+Fibo(n-2);
同理我们考虑不止两行的情况,假设枚举到了第i行,前i-2行已经密铺了,将每行的骨牌有无压缩至一个十进制的二进制对应位,这个so easy吧,ss,s分别表示上行i-1状态和本行i状态,就像其他的dp题一样,状态不是直接找的对应的来转化的,而是判断两个状态是否可以转化再转化,这个过程就是下面的solve。重点是ok()这个判断函数:
1.第i行的第j列为1,第i-1行的第j列为1,这样的话,说明第i行的第j列一定不是竖放而是横放否则会与第i-1行的第j列冲突
所以马上紧接着判断第i行第j+1列,如果是1,那么满足横放的规则,同时也要第i-1行第j+1列也要为1,否则的话这个格子没办法填充,
成立后向左移动两格
不满足上述条件的,就是两个不兼容或者不合法的状态
2.第i行第j列为1,第i-1行第j列为0,那么说明第i行第j列应该竖放并填充第i-1行第j列,成立后向左移动一格
3.第i行第j列为0,说明不放方块,那么第i-1行第j列必须为1,否则没法填充这个格子。若第i-1行第j列也为0,不兼容不合法
(至于第i行第j列这个格子空着干什么,其实就是留出来给第i+1行竖放的时候插进来的)
#include <iostream> #include<cstdio> #include<cstring> using namespace std; #define N 15 #define MAX (1<<11)+10 long long dp[N][MAX]; long long ans[N][N]; int n,m; bool init(int s) { for(int i=0;i<m;) { if(s&(1<<i)) { if(i==m-1) return false; if(s&(1<<(i+1)))i+=2; else return false; } else i++; } return true; } bool ok(int s,int ss) { for(int i=0;i<m;) { if(s&(1<<i)) { if(ss&(1<<i)) { if(i==m-1||!(s&(1<<(i+1)))||!(ss&(1<<(i+1))))return false; i+=2; } else i++; } else { if(ss&(1<<i))i++; else return false; } } return true; } void solve() { if(n<m) swap(m,n); memset(dp,0,sizeof(dp)); int maxn=(1<<m)-1; for(int i=0;i<=maxn;i++) if(init(i)) dp[1][i]=1; for(int i=2;i<=n;i++) for(int s=0;s<=maxn;s++) for(int ss=0;ss<=maxn;ss++) if(ok(s,ss)) dp[i][s]+=dp[i-1][ss]; printf("%I64d\n",dp[n][maxn]); } int main() { //freopen("cin.txt","r",stdin); while(~scanf("%d%d",&n,&m)) { if(m==0&&n==0) break; if(m*n%2) { printf("0\n"); continue; } solve(); } return 0; }