Time Limit: 3000MS | Memory Limit: 65536K | |
Total Submissions: 10132 | Accepted: 5865 |
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
Source
解题报告:很有意思的一道题。
在状态压缩专题里刷的,所以首先想到的就是类似于炮兵阵地的状态压缩和转移。
在一行中,我们用当前行1下一行0表示竖着放的砖,用00表示横着放的砖头。而两行的状态a,b,如果a|b==0,说明竖着的砖头不冲突。并且如果a|b的二进制形式中所有的0都是连续偶数的,如1001,就表示剩下的我们可以横着放,a,b可转移。
然后就是代码:
#include <cstring> #include <cstdio> #include <algorithm> #include <iostream> using namespace std; const int maxn=1<<11; #define LL long long LL dp[12][maxn]; LL res[12][12]; int n,m; bool ok(int a,int b) { if(a&b) return false; a|=b; bool two=false; int t=m; while(t--) { if(!(a&1)) { if(two) two=false; else two=true; } else if(two) return false; a>>=1; } if(two) return false; return true; } int main() { memset(res,-1,sizeof(res)); while(scanf("%d%d",&n,&m),n||m) { if(n*m%2) { puts("0"); continue; } else if(~res[n][m]) { printf("%I64d\n",res[n][m]); continue; } memset(dp,0,sizeof(dp)); dp[0][0]=1; int total=1<<m; for(int i=1;i<n;i++) for(int j=0;j<total;j++) for(int k=0;k<total;k++) if(ok(k,j)) dp[i][j]+=dp[i-1][k]; LL ans=0; for(int i=0;i<total;i++) if(ok(i,0)) ans+=dp[n-1][i]; printf("%I64d\n",res[n][m]=ans); } }
后来看到一个优化,在遍历状态j时,可以判断dp[i][j]是否等于0,等于0就不用计算了。大概优化到300MS。
再想一下,整个图形的对称性很强。例如,当前行1100与0011状态的方法数量一定是一致的。
上下也可以对称的,能不能根据这个来优化呢?例如7*10,我们遍历到第4行,下面还有3行。而3行的状态都求过了,直接判断是否可行并相乘即可。
实现时略有区别。7*10的例子只算到第3行,直接判断两个第3行的状态能否构成一个新行即可。代码如下:
#include <cstring> #include <cstdio> #include <algorithm> #include <iostream> using namespace std; #define LL long long LL dp[12][1<<11]; int n,m; bool ok(int a,int b) { if(a&b) return false; a|=b; bool two=false; int t=m; while(t--) { if(!(a&1)) { if(two) two=false; else two=true; } else if(two) return false; a>>=1; } if(two) return false; return true; } int main() { while(scanf("%d%d",&n,&m),n||m) { if( n%2 && m%2 ) { puts("0"); continue; } memset(dp,0,sizeof(dp)); dp[0][0]=1; int total=1<<m; int a=n/2,b=(n-1)/2; for(int i=1;i<=a;i++) for(int j=0;j<total;j++) if(dp[i-1][j]) for(int k=0;k<total;k++) if(ok(k,j)) dp[i][k]+=dp[i-1][j]; LL ans=0; for(int j=0;j<total;j++) if(dp[a][j]) for(int k=0;k<total;k++) if(ok(k,j)) ans+=dp[a][j]*dp[b][k]; printf("%I64d\n",ans); } }
网上也有插头DP的方法。因为还没开始学习插头DP,就研究了一下。代码如下:
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; long long dp[2][1<<11]; int main() { int n,m; while(scanf("%d%d",&n,&m),(n||m)) { int total=1<<m; int pre=0,now=1; memset(dp[now],0,sizeof(dp[now])); dp[now][0]=1; for(int i=0;i<n;i++) for(int j=0;j<m;j++) { swap(now,pre); memset(dp[now],0,sizeof(dp[now])); for(int S=0;S<total;S++) if( dp[pre][S] ) { dp[now][S^(1<<j)]+=dp[pre][S]; if( j && S&(1<<(j-1)) && !(S&(1<<j)) ) dp[now][S^(1<<(j-1))]+=dp[pre][S]; } } printf("%lld\n",dp[now][0]); } }