本题解是参考 算法竞赛进阶指南 视频所写,推荐去看这里面的视频题解。
所有的要点,都在注释中体现了
/*
核心:先放横着的,再放竖着的
总方案数:等于只放横着的小方块的合法方案数
如何判断,当前方案是否合法? 所有剩余位置,能否填充满竖着的小方块。
可以按列来看,每一列内部所有连续空着的小方块的数量需要是偶数个。(因为要放竖着的2x1的小方格)
状态表示:化0为整
f(i,j):表示前i-1列已经摆好,且从i-1列,伸出的小方格在第i列的状态是j的方案集合
状态转移:化整为0
考虑从i-2、i-1、i列。由于i-1到i列的划分已经固定,所以划分的依据是从i-2列伸出到i-1列的状态k来划分
条件:
1、由于要没有交集,所以: k & j == 0
2、由于i-1列已经固定,所以其连续空着的小方格数量必须要为偶数。就是i|j:中每一段连续的0是否是偶数
*/
import java.util.*;
public class Main {
static int N = 12, M = 1 << N;
static int n, m;
// f[i][j]:表示前i-1列已经摆好,且从i-1列,伸出的小方格在第i列的状态是j的方案的总数量
static long[][] f = new long[N][M];
// 这就是表示每一个数的二进制数,每一段连续的0是否是偶数
static boolean[] st = new boolean[M];
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
while(true) {
n = sc.nextInt(); m = sc.nextInt();
if(n == 0 && m == 0) break;
// 预处理这一列所表示的二进制数中,连续的0是否是偶数个。这里是满足条件2
for(int i = 0; i < 1 << n; i ++) {
int cnt = 0;
boolean is_valid = true;
// 查看这个二进制数中,每一段连续的0的个数是否是偶数
for(int j = 0; j < n; j ++) {
// 如果相与为1,就需要判断这一段0的个数
if(((i >> j) & 1) == 1) {
// 判断当前一段的0是否是偶数个
if((cnt & 1) == 1) {
is_valid = false;
break;
}
// 走到这里,就说明这一段的个数为偶数,这里需要将cnt清零,来计算下一段
cnt = 0;
}else cnt ++;
}
// 这里是看最后一段连续的0是否是偶数个。相与为1就代表为这一段是奇数个0
if((cnt & 1) == 1) is_valid = false;
st[i] = is_valid;
}
// 由于是多组数据,所以每一次都需要进行重置
for (int i = 0; i < N; i++) Arrays.fill(f[i], 0);
// 由于不存在-1列,所以从-1列伸出到第0列状态为0(就相当于没有伸出),
// 是一种合法方案,所以初始化为1。
// 其他状态 > 0,由于不存在-1列,所以其他都为0
f[0][0] = 1;
// 枚举时:需要考虑 i-2列、i-1列、i列
// 从i-2伸出到i-1列的状态是 k,从i-1列伸出到i列的状态是 j
// 枚举每一列,从第2列开始,因为没有-1列存在。
for(int i = 1; i <= m; i ++) {
// 这是枚举第i列
for(int j = 0; j < 1 << n; j ++) {
// 这是枚举第i-1列所有状态,也就是从i-2列伸出到i-1列
for(int k = 0; k < 1 << n; k ++) {
// 满足两个条件
if((j & k) == 0 && st[j | k]) {
f[i][j] += f[i - 1][k];
}
}
}
}
// 代表从下标m-1(也就是第m列)伸出的小方格的状态为0。
// 也就是第m列不横放小方格伸出到m+1列,就是我们最终求出的方案数
System.out.println(f[m][0]);
}
}
}