POJ2411 蒙德里安的梦想 题解

题目描述


 

求把 N×M 的棋盘分割成若干个 1×2 的的长方形,有多少种方案。

例如当 N=2,M=4 时,共有 5 种方案。当 N=2,M=3 时,共有 3 种方案。

如下图所示:

 

 

 输入


 

输入包含多组测试用例。

每组测试用例占一行,包含两个整数 N 和 M。

当输入用例 N=0,M=0 时,表示输入终止,且该用例无需处理。

输出


 

每个测试用例输出一个结果,每个结果占一行。

 

输入样例


 

 

1 2
1 3
1 4
2 2
2 3
2 4
2 11
4 11
0 0

 

输出样例


 

1
0
1
2
3
5
144
51205

数据范围


 

1N,M11

题解


 

这是一道状态压缩dp的入门( 模板)题

题目大意了解后,我们发现数据范围小的离谱,而这道题的状态非常大,所以我们考虑状压dp

接下来开始想状态表示和转移方程

这道题的核心思想就是:

我们可以先放横的,再放竖的;

这里我们用  f[i,j] 表示 已经填好了前 i - 1列,考虑从第i - 1列伸到第i列的所有方案数,并且状态为 j

按照正常的dp思路,我们都是考虑倒数第二个进行思考

不妨假设前i - 1行的状态为k 那么最后的转移方程显然是 f[i,j] += f[i - 1,k];

方程想好了考虑合法二字,什么时候合法呢?

我们容易发现

         1.j & k == 0(不能同时占有一个格子)

         2.所有连续的空格子的长度必须是偶数

 

只有同时满足上面两个条件才是合法状态;

我们不妨先预处理出来满足条件2的合法状态,至于条件1,可以在方程转移的时候进行特判

最后注意一点答案应该是 f[m][0];

 

代码如下:

#include<iostream>
#include <algorithm>
#include <cstring>
using namespace std;

const int N = 12,M = 1 << N;
long long f[N][M];
bool st[M];
int n,m;

int main()
{
    while(cin >> n >> m && (n || m))
    {
        for(int i = 0;i < 1 << n; ++ i)
        {
            int cnt = 0;
            st[i] = true;
            for(int j = 0; j < n; ++ j)
            {
                if(i >> j & 1)
                {
                    if(cnt & 1) st[i] = false;
                }
                else cnt ++;
            }
            if(cnt & 1) st[i] = false;
        } 
        memset(f,0,sizeof f);
       f[0][0] = 1;
       for(int i = 1; i <= m; ++ i)
          for(int j = 0; j < 1 << n; ++j)
             for(int k = 0; k < 1 << n; ++ k)
               if ((j & k) == 0 && (st[j | k])) f[i][j] += f[i - 1][k];
      cout << f[m][0] << endl;
    }
    
    return 0;
}
 
原文地址:https://www.cnblogs.com/yjyl0098/p/14620729.html