# 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;
}
posted @ 2020-07-14 14:27  yhsmer  阅读(192)  评论(0编辑  收藏  举报