AcWing 291 蒙德里安的梦想

题目描述:

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

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

如下图所示:

2411_1.jpg

输入格式

输入包含多组测试用例。

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

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

输出格式

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

数据范围

1≤N,M≤11

输入样例:

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

分析:

本题要求将n*m的格子划分为若干个1*2的长方形,求总的划分方案数,或者说在棋盘上放置若干个1*2的长方形使得它们刚好能够覆盖整个棋盘的方案数。长方形要么横放要么竖放,我们知道了所有放置横向方格的位置,其它空的地方自然全部放上竖的方格,也就是说,求放置的方案数等同于求放置横向方格的方案数。

状态表示:这里将二维压缩为一维,如果方格有五行,则第i列的状态可以表示为11011,其中1表示从该位置开始横向放置1*2的长方形,0则表示没有放置。则f[i][j]表示第i列状态为j的方案数。首先考虑状态转移的约束条件。

如上图所示,第k列第二行放置了一个横向的长方形,则对应的标志位为1,既然在第二行种放置了一个从第k列延展到第j列的横向长方形,则第二行第k列便无法再放置延展到下一列的横向长方形了,从而得到第一个约束条件,相邻两列的状态k & j = 0。

放完横向方格后,剩下的要能放下竖向方格且恰好能填满,也就是放完横向方格后的某一列连续空格的个数应该是偶数。所以某列如果有奇数个连续空格,则该状态非法,这是第二个约束条件。

我们知道,某列状态为10111,第一个数是1表示第一行有从该列扩展到下一列的横向长方形,也就是说,如果第一行第一列的状态为1,说明第一行的一二两列都被填充了,要想某个空格没被填充,需要该空格的状态不能为1,并且该空格左边的空格也不能是1,即k与j对应行的状态都是0才可以让该空格的不被填充。比如k = 10100,j = 01000,k | j = 11100,第j列表示最后两行都还未被覆盖,可以用竖向长方形进行填充。为了便于判断相邻两列的状态是否满足竖向连续空格个数是偶数的要求,可以先预处理它们的与运算结果,比如棋盘有5行,与运算的结果范围为0到2^5 - 1,与运算结果是11000表示三个连续空格不合法,而11100合法。预处理出了所有状态是否合法,我们在状态转移的过程中就可以直接对相邻的两个状态k | j判断是否满足第二个约束条件了。

下面分析状态转移方程以及边界情况,f[i][j]表示第i列状态是j的方案数,可以由上一行f[i-1][k]的状态转移而来,且如果有k1,k2,k3状态都可以转化为j,则f[i][j] = f[i-1][k1] + f[i-1][k2] + f[i-1][k3]。总而言之,状态k可以转移到j,则f[i][j] += f[i-1][k],这就是本题的状态转移方程。边界情况为,第0列必定全为0,所以f[0][0] = 1,从第1列递推到第m列,第m列的状态必然也全是0,因为不能延展突破棋盘边界,最终的方案数是f[m][0]。

状态转移过程的设计:状态转移从第一列转向第m列,故第一重循环是列数的枚举,我们还需要枚举i - 1列的所有状态以及第i列的所有状态,判断由第i-1列的合法状态可以推出多少种第i列的合法状态。合法的状态必然有若干个方案数,所以f[i][j] += f[i-1][k]可以使得f[i][j]增加,如果i-1列的某种状态非法,则由其转移得到的f[i][j]加上的方案数也不过是0。

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 12,M = 1 << N;
int n,m;
long long f[N][M];
bool st[M];
int main(){
    while(cin>>n>>m,n || m){
        for(int i = 0;i < 1<<n;i++){//预处理n位二进制数中包含奇数个连续1的非法状态
            int cnt = 0;
            st[i] = true;
            for(int j = 0;j < n;j++){
                if(i >> j & 1){//遇见1判断下前面的连续0是否是奇数
                    if(cnt&1)   st[i] = false;
                    cnt = 0;
                }
                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])//约束条件1和2
                    f[i][j] += f[i - 1][k];//合法则转移
        cout<<f[m][0]<<endl;
    }
    return 0;
}
发布了272 篇原创文章 · 获赞 26 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_30277239/article/details/103986576