【POJ 2411】Mondriaan's Dream(状压dp)
Time Limit: 3000MS | Memory Limit: 65536K | |
Total Submissions: 14107 | Accepted: 8152 |
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
一个比较明显的状压。给出容器size w*h 又固定了砖块大小1*2 砖块只有两种状态 一种横放一种竖放
设每个1x1的格子放为1未放为0 这样第i行放置状态只与i-1行有关 如果当前未知i-1行为0 那么i行一定要放 既该砖块竖放在i-1和i两行间
这样通过状压后从上往下一行行推 每次枚举状态 模拟判断是否合法即可 最后答案就是dp[w][h](因为最后一行必须铺满
代码如下:
#include <iostream> #include <cmath> #include <vector> #include <cstdlib> #include <cstdio> #include <cstring> #include <queue> #include <stack> #include <list> #include <algorithm> #include <map> #include <set> #define LL long long #define Pr pair<int,int> #define fread() freopen("in.in","r",stdin) #define fwrite() freopen("out.out","w",stdout) using namespace std; const int INF = 0x3f3f3f3f; const int msz = 10000; const int mod = 1e9+7; const double eps = 1e-8; LL dp[2][1<<11]; int h,w; bool cal(int pre,int now) { int cnt = 0; for(int i = 0; i < h; ++i) { if(!(pre&1)) { if(!(now&1)) return false; if(cnt&1) return false; cnt = 0; } else { if(!(now&1)) { if(cnt&1) return false; cnt = 0; }else cnt++; } pre >>= 1; now >>= 1; } return !(cnt%2); } bool can(int sit) { int cnt = 0; while(sit) { if(sit&1) cnt++; else { if(cnt&1) return false; cnt = 0; } sit >>= 1; } if(cnt&1) return false; return true; } int main() { //fread(); //fwrite(); while(~scanf("%d%d",&h,&w) && (h+w)) { if(h > w) swap(h,w); int tot = 1<<h; for(int i = 0; i < tot; ++i) dp[0][i] = can(i); int pos = 1; for(int i = 1; i < w; ++i, pos ^= 1) { memset(dp[pos],0,sizeof(dp[pos])); for(int j = 0; j < tot; ++j) for(int k = 0; k < tot; ++k) if(cal(j,k,h)) dp[pos][k] += dp[pos^1][j]; } printf("%lld\n",dp[pos^1][tot-1]); } return 0; }
然而会发现这种特别暴力的方法灰常灰常慢,多亏出题人良心,比较卡边过。。2000+ms
之后看讨论版许多用位运算做的,由于第i行只与第i-1行相关,i-1行为0的地方i行必须为1 i-1行为1的地方i行不限制
这样枚举第i-1行状态,对于每个状态j ~j&(1<<h)就是i行必放的状态(~j将j取反 1变0 0变1 之后与1<<h且运算 抛去超出状态的位
之后再通过搜索 在可进行选择的位置进行枚举判断 看能不能放横砖(不可放竖砖 因为对于第i行 放竖砖是对于i-1行而言 就是是竖放在i-1与i行之间 跟i+1行无关,在枚举i行状态时 进行刚才的取反和且运算才会出现竖放在i和i+1行间的砖
这样会减去对很多没有必要的状态的枚举 神剪枝!!!Orz
代码如下:
#include <iostream> #include <cmath> #include <vector> #include <cstdlib> #include <cstdio> #include <cstring> #include <queue> #include <stack> #include <list> #include <algorithm> #include <map> #include <set> #define LL long long #define Pr pair<int,int> #define fread() freopen("in.in","r",stdin) #define fwrite() freopen("out.out","w",stdout) using namespace std; const int INF = 0x3f3f3f3f; const int msz = 10000; const int mod = 1e9+7; const double eps = 1e-8; LL dp[2][1<<11]; LL add; int h,w; void cal(int id,int pos,int now) { if(pos == h) { dp[id][now] += add; return; } cal(id,pos+1,now); if(pos <= h-2 && ((now^(1<<pos))&(1<<pos)) && ((now^(1<<(pos+1)))&(1<<(pos+1)))) cal(id,pos+2,now|(1<<pos)|(1<<(pos+1))); } int main() { //fread(); //fwrite(); while(~scanf("%d%d",&h,&w) && (h+w)) { memset(dp[0],0,sizeof(dp)); add = 1; cal(0,0,0); int pos = 1; int tot = 1<<h; for(int i = 1; i < w; ++i, pos ^= 1) { memset(dp[pos],0,sizeof(dp[pos])); for(int j = 0; j < tot; ++j) if(dp[pos^1][j]) { add = dp[pos^1][j]; cal(pos,0,~j&(tot-1)); } } printf("%lld\n",dp[pos^1][tot-1]); } return 0; }