题目描述
输入描述
输出描述
数据范围
输入样例
1 2
1 3
1 4
2 2
2 3
2 4
2 11
4 11
0 0
输出样例
1
0
1
2
3
5
144
51205
本题中,采用了先放横的小方块,再在空位插入竖的小方块的解法,此时总方案数即为只放横着小方块的合法方案数。原因在于:对于某种放置完横的小方块的情况后,插入竖的小方块的方法一定是唯一的。因此本题可转化为在按某种方法放置了横的小方块后,剩余空位是否满足能够插满若干竖的小方块,满足该情况的方法数总和即为总方案数。
由于竖小方块只能在同一列内进行摆放,因此在摆放横小方块时,显然合法方案是在该列留下偶数个(也可以是0个)空格。
因此,我们可以对每一列中的合法状态进行一个预处理。其中,由于该列中的每一行都可以有两种情况,即:存在方块(1)或空格(0),因此 n 行共有 2 n 2^n 2n 种方案。
//每一列合法状态预处理
for (int i = 0; i < 1 << n; i ++){//此处的i表示的即为状态 若n=5 则由00000判断至11111
int cnt = 0;
st[i] = true;
for (int j = 0; j < n; j ++)//表示行数
if (i >> j & 1){//即取该状态的第j行的数
if (cnt & 1) st[i] = false; //cnt为在这位1之前存在了多少连续的0 若为奇数说明之前的空位不能插入竖方块
cnt = 0;//清零 用于记录下一段连续0的数量
}
else cnt ++;
if (cnt & 1) st[i] = false; //判断最后一段有多少个连续的0 若11110则不合法
}
对单列的合法情况处理完后,即可开始处理多列状态。不难发现横着的小方块需要占据两列的特殊性,为了进行规范,我们统一将某个横小方块的左侧所处的列的对应行标为 1 ,例如:
由图中可看出,第 i - 1 列和第 i 列的第一行中,红蓝色的小方块发生了冲突,这是由于我们默认将每个横小方块的左侧所在列作为标记点,但小方块的右侧也是会占据空间的。因此对于每一列,在考虑该列的合法状态的同时也需要兼顾上一列的对应行是否为 1,即两个相邻列的同一行不能够同时为 1,否则将出现方块重叠的情况。
同时还可以注意到,对于第 i 列,在兼顾了第 i - 1 列的状态下,此时的状态为 1101,显然对于该状态是无法在后续进行插入竖的小方块的。因此对于每一列,还需要考虑当列在兼顾了上一列的状态之后,是否符合之前预处理的合法状态。
因此,对于目前遍历到的某一列 i,可枚举该列和上一列的所有可能情况,并判断这两列的可能情况进行叠加后是否合法,若合法,则状态转移方程为 f [ i ] [ j ] += f [ i - 1] [ k ] ,即第 i 列的第 j 种状态由第 i - 1 列的第 k 中状态转移而来,注意累加在第 i + 1 列之前的所有合法状态数。
//状态转移
for (int i = 1; i <= m; i ++)
for (int j = 0; j < 1 << n; j ++)
for (int k = 0; k < 1 << n; k ++)
if ((j & k) == 0 && (st[j | k]))
//j&k==0 表示相邻两列的同一行不存在同时为1
//st[j|k] 意为判断第i-1列的k状态与第i列的j状态融合后是否满足先前的预处理结果
f[i][j] += f[i - 1][k]; //状态转移方程
参考代码
#include<bits/stdc++.h>
using namespace std;
const int N = 12, M = 1 << N;
int st[M];
long long f[N][M];
int main(){
int n, m;
while (cin >> n >> m && (n || m)){
for (int i = 0; i < 1 << n; i ++){
int cnt = 0;
st[i] = true;
for (int j = 0; j < n; j ++)
if (i >> j & 1){
if (cnt & 1) st[i] = false;
cnt = 0;
}
else cnt ++;
if (cnt & 1) st[i] = false;
}
memset(f, 0, sizeof f);
f[0][0] = 1;
for (int i = 1; i <= m; i ++)
for (int j = 0; j < 1 << n; j ++)
for (int k = 0; k < 1 << n; k ++)
if ((j & k) == 0 && (st[j | k]))
f[i][j] += f[i - 1][k];
cout << f[m][0] << endl;
}
return 0;
}