#include<iostream>
#include<algorithm>
#include<vector>
#include<cstring>
using namespace std;
const int N = 15, M = 1 << N;
long long f[N][M];
bool st[M];
int n, m;
vector<int> S[M];
int main()
{
while(cin>>n>>m, n || m){
for(int i = 0; i < 1 << n; i ++ ){//这里的i表示的是任何一列在这种01排列情况下是合法的(1表示从这一列的这个位置有横向摆放的砖,可能是上一列伸进来的,也可能是这一列伸到下一列的
bool v = true;
int res = 0;//res 记录这种情况下一列连续的0的个数
for(int j = 0; j < n; j ++ ){//j表示行数,从这一列从上往下遍历
if(i >> j & 1){//如果这一列的第j个为砖头,判断之前有几个连续的不是砖头的位置
if(res & 1){//如果连续不是砖头的位置数是奇数,代表这种情况不合法,标记false
v = false;
break;
}
res = 0;//如果这种情况合法,就让res = 0,继续向下遍历
}
else res ++ ;//如果这一列第j个位置不是砖头,就让res ++ ;
}
if(res & 1) v = false;//判断最后一段没有砖头的位置的数量的奇偶性
st[i] = v;//记录这种状态是否合法
}
//筛选出所有情况是否合法之后,就开始将合法的情况加入S中
for(int i = 0; i < 1 << n; i ++ ){//表示第i列的所有状态
S[i].clear();
for(int j = 0; j < 1 << n; j ++ ){//表示第i - 1列的所有状态
if((i & j) == 0 && st[i | j]){
//i表示第i列的状态,j表示第i - 1列的状态,当两列状态不冲突时(即i & j == 0),表示这两列相邻,例如:i = 10010, j = 01101,这时表示这两列砖头的位置排列不冲突
//st[]记录的是所有合法情况,i的情况表示第i- 2列伸到第i - 1列的砖头排列情况,j表示第i - 1列伸到第i列的砖头排列情况,i | j表示第i - 1列的砖头排列情况,如果这种排列情况合法
//例如:i = 100001, j = 010010,i | j = 110011,这就是一种合法情况
S[i].push_back(j);//将i - 1列加入S中,记录这一列可以这么排列
}
}
}
memset(f, 0, sizeof(f));
memset(st, 0, sizeof(st));
f[0][0] = 1;//初始化,表示第0列伸向第1列的砖头数为0的合法数情况是1
for(int i = 1; i <= m; i ++ ){//i表示列数
for(int j = 0 ; j < 1 << n; j ++ ){//遍历这一列所有合法的状态
for(auto k : S[j]){//如果j状态可行,就状态转移
f[i][j] += f[i - 1][k];//当前列的合法方案数就等于i - 1列合法方案数的累加
}
}
}
cout<<f[m][0]<<endl;//m表示第m + 1列,f[m][0]表示第m+1列都是0的状态,即第m列没有伸向m+1列的砖头
}
return 0;
}
AcWing 291 蒙德里安的梦想 题解 (动态规划—DP—状态压缩DP)
最新推荐文章于 2023-08-26 10:18:26 发布