# Acwing 291蒙德里安的梦想

Acwing 291蒙德里安的梦想

题意

(N*M)的棋盘分割成若干个(1*2)的长方形的方案总数。

思路

一共有N行,考虑以某一行为分界,将整个棋盘分成两半,第(i)行的状态通过第(i-1)行转移过来。

由于是分割,(横放的长方形只占一行,不会影响下一行的状态,用于填充该行的空)。所以只需要考虑竖着放的长方形有多少种方法,剩下的空都使用横放的长方形填充(只有一种方案),竖放的长方形的方案数就是答案。

(f[i][j])表示第(i)行每一列是否有(1*2)长方形的起点,该行每一列的状态通过二进制压缩在j中,就是说(j)的二进制的每一位的10表示对应列选还是不选((j=(5)_{10}=(101)_2)表示第一三列有长方形的起点,第二列则没有,该列是否有长方形终点暂时不用管,之后处理会用if判断)。

对于每一行放置完竖放的长方形后,留下来的空,如果有连续的奇数个空连在一起,就无法填充横放的长方形,那么这个竖放长方形的方案就不合理。需要预处理每一行的每一种竖放长方形的方案是否合理。

代码

#include <bits/stdc++.h>
using namespace std;
const int N=12;
int n,m;
bool isok[1<<N];
long long f[N][1<<N];

int main(){
    while (cin>>n>>m,n||m){

        for(int i=0;i<1<<m;i++){//枚举每一列是否有使用,有使用代表该列有出现长方形的起点或者终点
            isok[i]=true;
            int cnt=0;
            for(int j=0;j<m;j++){
                if(i>>j&1){
                    if(cnt&1){isok[i]=false;break;}//连续的奇数个空格,标记该状态不可用
                    cnt=0;
                }
                else cnt++;
            }
            if(cnt&1)isok[i]=false;
        }

        memset(f,0,sizeof f);
        f[0][0]=1;
        for(int i=1;i<=n;i++){//枚举每一行
            for(int j=0;j<1<<m;j++)//枚举该行每一列的状态
                for(int k=0;k<1<<m;k++){//枚举该行上一个状态,也就是上一行的列的使用状态
                    if(j&k || !isok[j|k])continue;//j,k枚举的是列是否出现起点,如果k出现起点,那么下一行k的位置就会出现终点,如果该列又出现起点就会冲突,(j|k)代表了第i行被长方形占据的位置(包括起点和终点)
                    f[i][j]+=f[i-1][k];
                }
        }
        cout<<f[n][0]<<endl;
    }
    return 0;
}
原文地址:https://www.cnblogs.com/sstealer/p/13298906.html