You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

63 lines
3.4 KiB

2 years ago
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 12;
const int M = 1 << N;
int n, m;
LL f[N][M]; // 已经摆完前i列并且第i列的状态为j的所有方案
vector<int> v[M]; // 对于每个状态而言,能转转移到它的状态有哪些,预处理一下(二维数组)
int ok[M]; // 某种状态是否合法就是是不是存在奇数个连续0
int main() {
while (cin >> n >> m, n + m) {
// ① 预处理枚举行数n的每个二进制位,可以枚举出每种可能出现的状态对应的二进制表示,这些状态有些是不合法的
// 只有不存在连续奇数个数字0的状态才是合法的一旦n确定了这个有效状态是可以预处理出来的
for (int i = 0; i < 1 << n; i++) {
int cnt = 0; // 连续0的个数
ok[i] = 1; // 初始设置此状态是合法的
for (int j = 0; j < n; j++) // 遍历此状态的每一个二进制位,开始检查
if (i >> j & 1) { // 如果本位是1,表示连续0发生中断需要统计连续0的个数并且记得清空cnt
if (cnt & 1) { // 奇数个连续0, (cnt & 1) = (cnt % 2 >0)
ok[i] = 0; // 连续0发生中断,此状态为不合法状态
break; // 不用再往后看了,一次失足就不挽救
}
cnt = 0; // 连续个零计数重新开始
} else
cnt++; // 连续0的计数器++
// 最后一个cnt++后依然可能有连续奇数个举个栗子n=4=(0100)_2完成数位枚举后cnt=1,也就是高位存在奇数个0
if (cnt & 1) ok[i] = 0;
}
// ② 预处理:枚举每个状态,获取可能是从哪些有效状态转移过来
for (int i = 0; i < 1 << n; i++) {
// 多组数据,每次预处理时清空一下
// vector<int> v[M] 是一个二维数组初始化比较麻烦需要用循环遍历第一维然后再v[i].clear()进行清空
v[i].clear();
// 状态i,从哪些状态转化而来?
for (int j = 0; j < 1 << n; j++) // j为前序状态
// (1) i & j==0 同一行不能同时探出小方格,那样会有重叠
// (2) 解释一下ok[i | j]
// 比如: 01000 | 10101 = 11101,描述当前完成状态叠加后的最终状态,在预处理的数组中找一下,是不是合法状态
if ((i & j) == 0 && ok[i | j]) v[i].push_back(j);
}
// 多组数据,每次清零
memset(f, 0, sizeof f);
// 初始方案数
f[0][0] = 1; // 可以理解为 从虚拟的第0开始第一个0还没有向右探出小方格第二个0此时的方案数只有1种。
// 如果你想为什么不是0种下面的递推关系就会让你明白0做为基底就啥也递推不出来了。
// DP正式开始
for (int i = 1; i <= m; i++)
for (int j = 0; j < 1 << n; j++) // 遍历第i列的所有状态j
for (auto k : v[j]) // 遍历第i-1列的所有状态k
f[i][j] += f[i - 1][k]; // 每个合法状态,均需从它的前序有效状态转移而来
// 输出答案
cout << f[m][0] << endl;
}
return 0;
}