蒙德里安的梦想

AcWing

题意分析

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;
}
posted @ 2020-11-12 08:24  Kersen  阅读(431)  评论(0)    收藏  举报