题意:
h*w的区域铺上1*2的砖,问有几种铺法.
思路:
(例程来源:
http://gisyhy.blog.163.com/blog/static/129390343200992441558735/
http://blog.sina.com.cn/s/blog_62ebd4410100h081.html
部分解读:
http://blog.sina.com.cn/s/blog_68b1a51b0100j91c.html#post)
解法一
一行一行铺,判断后一行能否从前一行转移而来.
具体见代码.
#include <iostream> #include <cstdio> #include <cstring> using namespace std; ///624K 1954MS #define N 11+1 #define S (1<<12) #define LL long long LL dp[N][S]; bool a[S]; int n,Maxn,h,w; bool Ok(int i)//初始化第1行状态为i时是否容许 { for(int k = 0 ; k < w; ) { if(i & (1<<k)) { if(k == w-1 || !(i & (1<<(k+1))))///竖放只能靠脑补,初始化的时候只有横放 return false; k+= 2; } else k ++; } return true; } /** 1、如果第i行中有0,则第i-1行一定为1; 2、如果第i行中为1的x列第i-1行为0,说明第i行肯定是竖着放的;(上一行0变1是脑补了) 3、如果第i行中为1的x列第i-1行的该列也为1,可能性只有一个, 第i行是横放的,所以第i行的x+1列也必须为1, 又因为第i行的x+1列为1是因为横着放的,所以第i-1行的x+1列也必须为1。 **/ bool Pk(int i,int j)//判断i和j状态有无冲突 { int k; for(k = 0; k < w; ) { if((i & (1<<k)) == 0) { if((j & (1<<k)) == 0)//如果第i行为0,则第j行一定得为1 return false; k ++; } else { if(j & (1<<k)) if(k == w-1 || ((i & (1<<(k+1))) && (j & (1<<(k+1)))) == 0) return false; else k +=2; else k++; } } return true; } int main() { while(scanf("%d %d",&h,&w),h && w) { int i,j,k; if(h < w)//将状态数取小,优化处理 i = h ,h = w,w =i; Maxn = (1<<w)-1;///闭区间 memset(dp,0,sizeof(dp)); for(i = 0 ; i <= Maxn; i++)//初始化第1行 {///就是合法的计数,不合法的留空 if(Ok(i)) dp[h][i] =1;///每种情况都试一遍,从下往上填的 } for(k = h-1 ; k >=1; k --)//枚举第k行 { for(i = 0 ; i <= Maxn; i++)//当前行的状态 { for(j = 0; j <= Maxn ; j++) //前一行的状态 { if(Pk(i,j))///只是单纯的判断i状态能否由j状态转移而来 dp[k][i] +=dp[k+1][j]; } } } ///填了好多不必要的空格... cout<<dp[1][Maxn]<<endl;///输出最后一行全部填满的情况 } return 0; }
加了两个优化:奇数面积直接输0;虚拟第0行,把第1行纳入check函数中(因为第一行就是check的特例~)
时间减少了1000MS....
#include <cstring> #include <cstdio> using namespace std; typedef long long ll; const int N = 12; const int S = 1 << 12; ll dp[N][S]; int maxs,h,w; ///548K 985MS bool ck(int i, int j) { for(int k=0;k<w; ) { if(!(i & (1<<k))) {///当前k位为0 if(!(j & (1<<k))) {///前一行k位为0 return false; } k++; } else { if(j & (1<<k)) {///k 位都为1 if(k==w-1 || !(i & (1 << (k+1)) && j & (1 << (k+1))) ) return false;///均为横放,下一位也都必须是1.当然首先得有下一位 else k += 2;///满足的话就越过一格 } else k++;///竖放 } } return true; } int main() { while(scanf("%d %d",&w,&h) && (h+w)) { if((h*w)%2) { printf("0\n"); continue; } if(h<w) { int tmp = h; h = w; w = tmp; } maxs = 1<<w; memset(dp,0,sizeof(dp)); dp[0][maxs-1] = 1; ///虚拟第0行,这样就可以直接从第1行开始填数了.第0行只有一种全填满的方案 for(int i=1;i<=h;i++) { for(int j=0;j<maxs;j++) { for(int k=0;k<maxs;k++) { if(ck(j,k)) dp[i][j] += dp[i-1][k]; } } } printf("%I64d\n",dp[h][maxs-1]); } }
因为solve是递归的,所以是从最后一行往前填的.
state中的1表示(横放或竖放的)被占据的格子,它的前一行对应状态i,i中的1表示(横放或竖放)被占据的格子.
0表示其下state上的1为竖放(也就是说按照时间顺序,填i的时候该位置保留为0,填state的时候该0变为1).
这样dp[state][n]表示第n行为state状态,1~n-1行填满的方案数.
这样的话可以发现和解法一是完全等效的,也就可以看出其充分性.
#include <cstdio> #include <cstring> using namespace std; //360K 32MS __int64 dp[2049][12];//记忆化搜索 __int64 num[12][12];//保存已经计算的结果不重复计算 int n,m,power; inline bool exam(int x)//检查状态x是否满足全部是横放 { int i = 0; while(i < m) { int sum = 0; while((x & (1<<i))) { sum ++; //找到连续的1 i++; } if(sum % 2) return(false);///遇到连续的1的个数为奇数,就判错 i++; } return(true);///全部检查完毕,都为偶,判对 } __int64 solve(int state, int n) { __int64 sum = 0; if(n == 1)//边界条件 {///表示第0行(虚拟)全填满1,state就表示公共的连续1 if(exam(state)) return(1); return(0); } else { int last = ~state; last = last & power;//找到state中0的位置标为1 if(dp[state][n] != -1) return(dp[state][n]);//已经搜索过 for(int i = 0; i <= power; i++)///遍历所有的方案 { if((i & last) != last) continue;///state中0的位置上i中并不都为1,pass int tmp = state & i;///state和i中都为1的位置标为1 ///至此,保证了当前行为0,前一行对应1;当前行为1且前一行也为1时,总是有连续的偶数个; ///还剩下一种情况就是当前行为1而前一行为0,此时前一行的0脑补为1,认为是当前行的1竖放了!!! if(exam(tmp)) sum += solve(i,n-1);//如果满足条件2 } dp[state][n] = sum;//保存结果 } return(sum); } int main() { memset(num,-1,sizeof(num)); while(scanf("%d%d",&n,&m) == 2 && n && m) { if(n * m % 2) { printf("0\n"); continue; } if(m > n) { n = m ^ n;///保存m与n相异的位于n中 m = m ^ n;///将m中与n相异的位翻转而剩余不变,恰好是把m变成n n = m ^ n;///把n的值再翻转为m存在n中 } if(num[n][m] != -1) { printf("%I64d\n",num[n][m]); //已经算过了 continue; } power = (1<<m) - 1; memset(dp,-1,sizeof(dp)); num[n][m] = solve((1<<m)-1,n); printf("%I64d\n",num[n][m]); } return(0); }仍然是自己敲一遍~
#include <cstdio> #include <cstring> using namespace std; typedef long long ll; const int N = 12; ll dp[1<<(N-1)][N];///这里真的是太精打细算->11是奇数,而宽度又尽量小,所以实际宽度最大为10... ll num[N][N]; int h,w,maxs; bool ck(int i) { int sum = 0; for(int k=0;k<w; ) { while(k<w && (i & (1<<k))) { sum++; k++; } if(sum % 2) return false; sum = 0; k++;///漏掉了哦~ } return true; } ll dfs(int state, int i) { if(i==1) { if(ck(state)) return 1; return 0; } if(dp[state][i]!=-1) return dp[state][i]; ll ans = 0; for(int s=0;s<maxs;s++) { int zero = ~state; zero = zero & (maxs-1); if((zero & s) != zero) continue;///state中0不全对应s中1,不合法 int tmp = s & state;///common 1 if(ck(tmp))///even, valid ans += dfs(s,i-1); } dp[state][i] = ans; return ans; } int main() { memset(num,-1,sizeof(num)); while(scanf("%d %d",&w,&h)==2 && (w+h)) { if(w*h%2) { printf("0\n"); continue; } if(w>h) { h = w ^ h; w = w ^ h; h = w ^ h; } if(num[h][w]!=-1) { printf("%I64d\n",num[h][w]); continue; } maxs = 1<<w; memset(dp,-1,sizeof(dp)); num[h][w] = dfs(maxs-1,h); printf("%I64d\n",num[h][w]); } }
#include <cstdio> using namespace std; typedef long long ll; const int N = 12; ll num[N][N] = { {0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {1, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {1, 1, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1}, {1, 0, 3, 0, -1, -1, -1, -1, -1, -1, -1, -1}, {1, 1, 5, 11, 36, -1, -1, -1, -1, -1, -1, -1}, {1, 0, 8, 0, 95, 0, -1, -1, -1, -1, -1, -1}, {1, 1, 13, 41, 281, 1183, 6728, -1, -1, -1, -1, -1}, {1, 0, 21, 0, 781, 0, 31529, 0, -1, -1, -1, -1}, {1, 1, 34, 153, 2245, 14824, 167089, 1292697, 12988816, -1, -1, -1}, {1, 0, 55, 0, 6336, 0, 817991, 0, 108435745, 0, -1, -1}, {1, 1, 89, 571, 18061, 185921, 4213133, 53175517, 1031151241, 14479521761, 258584046368, -1}, {1, 0, 144, 0, 51205, 0, 21001799, 0, 8940739824, 0, 3852472573499, 0} }; int h,w; int main() { while(scanf("%d %d",&h,&w)==2 && (h+w)) { if(w>h) { h = w ^ h; w = w ^ h; h = w ^ h; } printf("%I64d\n",num[h][w]); } }