题解
AcWing 291. 蒙德里安的梦想(《算法竞赛进阶指南》打卡活动)
摆放方块的时候,先放横着的,再放竖着的,其实放完横着的,竖着的也定下来了
总方案数等于只放着的小方块数的合法方案数
如何判断当前方案合法?
每列空余位置,每一块长度均为偶数,即为合法
f
i
,
j
f_i,j
fi,j 表示第
i
i
i 列被第
i
−
1
i-1
i−1 列上横着的
1
×
2
1\times 2
1×2 的方块占用的整列状态
j
j
j 的方案数
用图表示:
如果没有多组案例, 11 ∗ 2 11 ∗ 2 11 = 4 e 7 11*2^{11}*2^{11}=4e^7 11∗211∗211=4e7 的暴力显然是可以的,
但是,有了就得优化……
优化1:预处理各种状态的合法性
优化2:统计前后两列之间所有合法的状态,抛弃不合法的
这样就可以去掉一个 for
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 12;
int n, m;
ll f[N][1 << N];
bool st[1 << N];//当前这一列 剩余部分中连续区间长度是否为偶数
vector<int> state[1 << N];//所有合法状态
//哈密顿是集合的问题
//这里是连通性的问题
int main() {
ios::sync_with_stdio(0);
while (cin >> n >> m, n && m) {
int lim = 1 << n;
// 状态压缩1:
// 预处理判断各种状态下每一列剩余部分的合法性
for (int i = 0; i < lim; ++i) {
int cnt = 0;
bool valid = true;
for (int j = 0; j < n; ++j) {
if ((i >> j) & 1) {
if (cnt & 1) {
valid = false;
break;
}
cnt = 0;
} else cnt++;
}
// 务必注意处理边界 ...11000 这种情况
if (cnt & 1) valid = false;
st[i] = valid;
}
// 状态压缩2:
// 只统计有效的状态 无效的状态直接扔了
for (int i = 0; i < lim; ++i) {
state[i].clear();
for (int j = 0; j < lim; ++j) {
if ((i & j) == 0 && st[i | j])
state[i].push_back(j);
}
}
memset(f, 0, sizeof(f));
//第一列开头肯定是没有上一列延伸出来的方块
f[0][0] = 1;
for (int i = 1; i <= m; ++i) {
for (int j = 0; j < lim; ++j) {
for (auto k:state[j])
f[i][j] += f[i - 1][k];
}
}
// 统计的是0 ~ m-1列
//如果最后一行合法的话 第m列的状态一定为0 第m-1列不可以有横着的方块延伸到第m列上来
cout << f[m][0] << endl;
}
return 0;
}