https://www.acwing.com/problem/content/293/
题意
求把N × \times × M的棋盘分割成1 × \times × 2的长方形有多少种方案
思路
由于竖向摆放的长方形情况可被横向摆放的长方形唯一确定,所以考虑横向摆放即可。
定义 d p dp dp数组的含义为已将前 i i i-1列排好,并以 j j j作为状态压缩存储从 i i i-1列伸出到 i i i列的所有方案。对于列数,最大共有 11 11 11列,多开一列以便取答案。对于状态数,共有 2 11 2^{11} 211种状态。
所谓状态压缩,是把具有 m m m位的 b o o l bool bool数组以 m m m位二进制整数表示的方法,也可以使用 S T L STL STL的 b i t s e t bitset bitset实现。
考虑进行如下优化
- 对于横向排布长方形后不能够竖向摆满长方形,即连续的 0 0 0个数为奇数的状态,予以去除。
- 对于从 i i i-2列伸出到第 i i i-1列,与从第 i i i-1列伸出到第i列的长方形发生冲突的情况,予以去除。这一步可将第 i i i-2列的状态i和第 i i i-1列的状态j相与,检查是否为 0 0 0,从而判断是否因有重叠部分而不能摆放。
对于 d p [ 0 ] [ 0 ] dp[0][0] dp[0][0],其代表第1列既无伸出,亦没有伸入(因为第1列前并无任何列存在)的情况,所以唯一的摆放方式是将所有长方形竖向摆放, d p [ 0 ] [ 0 ] = 1 dp[0][0]=1 dp[0][0]=1。
代码中各数组的含义如下
- d p [ N ] [ M ] dp[N][M] dp[N][M],动态规划数组,第一维为列数,第二维为状态。
- s t [ M ] st[M] st[M],存储当前考察的状态是否因有连续奇数个 0 0 0而无效。
- s t a t e state state,用于记录所有状态组合。
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
int n, m;
const int N = 12;
const int M = 1 << N;
vector<vector<int>> state(M); // 存储合法状态
long long f[N][M]; // 第一维代表列 第二维代表状态
bool st[M]; // 当前列的连续0是否为偶数个
int main()
{
while (cin >> n >> m, n || m)
{
for (int i = 0; i < (1 << n); i++) // 预处理出列中只有偶数个0的状态以确保可被摆放满
{
bool is_valid = true;
int zero_cnt = 0;
for (int j = 0; j < n; j++)
{
if (i >> j & 1)
{ // 取出被状态压缩的每一位
if (zero_cnt & 1) // 奇数
{
is_valid = false;
break;
}
zero_cnt=0;
}
else
zero_cnt++;
}
if (zero_cnt & 1)
is_valid = false;
st[i] = is_valid;
}
for (int i = 0; i < (1 << n); i++)
{ // state[i]代表第i列状态 而j搜索的是第i-1列的状态
state[i].clear();
for (int j = 0; j < (1 << n); j++)
{
if ((i & j) == 0 && st[i | j])
{
state[i].push_back(j);
}
}
}
memset(f, 0, sizeof f);
f[0][0] = 1; // 第0列没有伸过来的 所以只有竖着一种摆法
for (int i = 1; i <= m; i++)
{
for (int j = 0; j < (1 << n); j++)
{
for (auto k : state[j])
{
f[i][j] += f[i - 1][k]; // 从合法状态转移
}
}
}
cout << f[m][0] << endl; // 答案是使m+1列没有插出来的方案数
}
return 0;
}