蒙德里安的梦想
题意分析
rua,可以分割棋盘,但是我们发现分割棋盘之后会有好多长方形被拦腰折断,所以可以没被折断的看做 0,折断的看做 1,那么下一行中那些折断的就必须看做 0, 剩下的可以是1 也可以是 0。
思路
用 \(f_{i,j}\) 表示第 i 行的形态 为 j 时,前 i 行的分割方案的总数。
第 \(i-1\) 的形态 k 转移到第 i 行的 j 形态,当且仅当:
- j 和 k 的 \(\&\) 结果是 0, 这保证了每个数字 1 下方必须是数字 0 ,代表继续补全上比方竖着的长方形。
- j 和 k 的 \(\big|\) 每一段连续的 0 必须有偶数个,代表了横着放的长方形。
我们可以 DP 求出 \([0, 2^M - 1]\) 内所有满足“二进制表示下每一段都是连续的0有偶数个”的整数,记录下来。
不难发现
\[\displaystyle f_{i.j} = \sum_{j \ \& \ k= 0 且 j \big| k \in s} f_{i-1, k}
\]
code
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define ll long long
#define N 100010
#define M 2050
using namespace std;
const int mod = 1e9+7;
const int inf = 0x3f3f3f3f;
int n, m;
int sy[M]; ll f[20][M];
int read() {
int s = 0, f = 0; char ch = getchar();
while (!isdigit(ch)) f |= (ch == '-'), ch = getchar();
while (isdigit(ch)) s = s * 10 + (ch ^ 48), ch = getchar();
return f ? -s : s;
}
int main() {
n = read(), m = read();
while (n != 0 || m != 0) {
memset(f, 0, sizeof f);
memset(sy, 0, sizeof sy);
for (int i = 0; i < (1 << m); i++) {
bool cnt = 0, odd = 0;
for (int j = 0; j < m; j++)
if (i >> j & 1) odd |= cnt, cnt = 0;
else cnt ^= 1;
sy[i] = (odd | cnt ? 0 : 1);
}
f[0][0] = 1;
for (int i = 1; i <= n; i++)
for (int j = 0; j < (1 << m); j++) {
f[i][j] = 0;
for (int k = 0; k < (1 << m); k++)
if ((j & k) == 0 && sy[j | k])
f[i][j] += f[i - 1][k];
}
printf("%lld\n", f[n][0]);
n = read(), m = read();
}
return 0;
}

浙公网安备 33010602011771号