[ABC296Ex] Unite 题解

考虑状压 dp。

\(f_{i,j,s}\) 表示当前正在决策坐标为 \((i,j)\) 的格子,且列状态为 \(s\)。其中列状态维护了当前轮廓线上的连通块,我们可以使用最小表示法来简单维护。

(为什么不用广义括号序列?因为其涉及到 \(5\) 个可选值,由于 \(m\le 7\),所以这两个都需要用到八进制,而最小表示法显然要好写很多。)

如果我们选择将当前格子变成黑色,那么相当于合并了当前格上面和左边的两个连通块。

如果我们保持当前格子为白色,那么啥都不变,但要注意,这可能会导致某个连通块被孤立导致错解。

直接统计答案即可。

#include <iostream>
#include <map>

using namespace std;

const int kN = 101, kM = 7;
const int kL = 1 << 3 * kM;

int n, m, o, l, li, ans = 1e9, gc[kL], gt[kL][kM], ct[kL][8];
char a[kN][kM];
map<int, int> f[2];

int G(int s, int i) { return i < 0 ? 0 : (s >> i * 3 & 7); }
int S(int s, int i, int v) { return s ^ (G(s, i) << i * 3) ^ (v << i * 3); }
void U(int i, int v) {
  auto p = f[o].find(i);
  if (p != f[o].end()) {
    p->second = min(p->second, v);
  } else {
    f[o][i] = v;
  }
}

int main() {
  ios_base::sync_with_stdio(0), cin.tie(0);
  cin >> n >> m;
  l = 1 << 3 * m;
  for (int i = 0; i < l; ++i) {
    for (int j = 0; j < m; ++j) {
      ++ct[i][G(i, j)];
    }
    for (int j = 1; j <= m; ++j) {
      if (!ct[i][j]) {
        gc[i] = j;
        break;
      }
    }
    for (int j = 1; j < m; ++j) {
      int vl = G(i, j - 1), vu = G(i, j);
      gt[i][j] = i;
      for (int k = 0; k < m; ++k) {
        if (G(gt[i][j], k) == vu) {
          gt[i][j] = S(gt[i][j], k, vl);
        }
      }
    }
  }
  for (int i = 1; i <= n; ++i) {
    for (int j = 0; j < m; ++j) {
      cin >> a[i][j];
    }
  }
  for (int i = n; i >= 1; --i) {
    for (int j = m - 1; j >= 0; --j) {
      if (a[i][j] == '#') {
        li = i;
        break;
      }
    }
    if (li) {
      break;
    }
  }
  f[o][0] = 0;
  for (int i = 1; i <= li; ++i) {
    for (int j = 0; j < m; ++j) {
      o ^= 1;
      f[o].clear();
      for (auto k : f[!o]) {
        int s = k.first, fv = k.second;
        int vl = G(s, j - 1), vu = G(s, j), v = (a[i][j] == '.');
        if (!vl && !vu) {  // 新开一个连通块
          U(S(s, j, gc[s]), fv + v);
        } else if (vl && !vu) {  // 接上左边的连通块
          U(S(s, j, vl), fv + v);
        } else if (!vl && vu) {  // 接上上面的连通块
          U(s, fv + v);
        } else {  // 将左边和上面的连通块接起来
          U(gt[s][j], fv + v);
        }
        if (a[i][j] == '.') {  // 不涂黑
          int vu = G(s, j);
          if (!vu || ct[s][vu] > 1) {  // 不能孤立某个连通块
            U(S(s, j, 0), fv);
          }
        }
      }
    }
  }
  for (auto p : f[o]) {
    int i = p.first, fv = p.second;
    int g = 0;
    for (int j = 0; j < m; ++j) {
      int v = G(i, j);
      if (!g) {
        g = v;
      } else if (v && v != g) {
        g = -1;
        break;
      }
    }
    if (~g) {
      ans = min(ans, fv);
    }
  }
  cout << ans;
  return 0;
}
posted @ 2023-04-14 21:32  bykem  阅读(32)  评论(0)    收藏  举报