有关连通性的状压 DP: 插头 DP

插头 DP (Plug Dynamic Programming)

陈丹琦在 2008 年引入国内 OI 界的插头 DP 是用来解决一些和连通性有关的问题的状态压缩算法. 一般采用轮廓线的方式逐格转移.

经典问题是给一张有障碍的棋盘, 求有多少条不同的回路可以覆盖所有无障碍的格点.

状态

显然我们如果仅维护插头存在性, 那么下面这两种情况就成了同一种状态.

image.png

我们不希望第一种情况中的 \(3\)\(4\) 的插头被连起来, 但是允许第二种情况中 \(3\)\(4\) 的插头被连起来. 所以我们需要维护轮廓线上每个插头的连通性.

可以用最小表示法表示每个插头所在的连通块. 用一个 \(\lfloor \frac 1m \rfloor\) 进制数表示轮廓线的状态. 每一位表示轮廓线上每个位置的状态, 这一位是 \(0\) 表示这个位置没有插头, 否则表示这是轮廓线上出现的第几个插头所属的连通块. 通过预处理处理出合法状态集合再进行转移.

还有一个方法是把每一个插头看作是括号, 上括号和下括号作为一个连通块在轮廓线上的两端. 我们用一个三进制数表示状态. \(0\) 表示此位置无插头, \(1\) 表示此位置为上括号插头, \(2\) 表示为下括号插头.

转移

实践证明括号表示法在各方面都比较优秀, 接下来考虑如何转移.

我们每次转移有三种可能的结果:

  • 连接两个连通块
  • 新建一个连通块
  • 延伸一个连通块

分别对应了转移的格子有 \(2\) 个插头相邻, 有 \(0\) 个插头相邻, 有 \(1\) 个插头相邻的情况.

对于没有插头相邻的情况, 我们只有一种转移, 就是使这个格子的右边和下面都存在插头, 变成一个新的连通块.

对于有两个插头相邻的情况, 我们需要进行选择, 转移还是不转移. 如果两个插头都是上括号或都是下括号, 可以转移, 这会连接两个连通块, 需要某个插头的另一半改变自己的类型. 这就需要预处理每个状态中插头的另一半的位置. 如果左边的插头是下括号, 右边插头是上括号, 也是可以转移的, 且不需要改变别的括号类型. 左边的插头是上括号, 右边插头是下括号的情况是不可以转移的, 这会使得一个连通块被闭合. 这个状态只能在最后一个无障碍的格子才能被转移.

对于只有一个插头相邻的情况, 这个格子的一端肯定需要连接这个插头, 另一端可以在右边或下边随意找一个位置作为自己的第二个插头, 这个插头类型继承自己相邻的格子的插头类型.

因为位运算的效率高于取余, 所以一般用 \(4\) 进制数表示 \(3\) 进制的状态.

代码实现

DFS 求出可行的状态, 给每个状态编号, 我们用哈希表把状态映射到编号. (我甚至可以滚动数组)

More details are in the code.

unordered_map<unsigned, unsigned> List;
unsigned long long f[42005], Tmp[42005];
unsigned g[42005][13], Find[42005], Stack[13], STop(0), Con(0), Now(0);
unsigned A, B, C, D, t, m, n, N;
unsigned Cnt(0), Ans(0);
char a[13][13], Flg(0);
inline void Nxt(unsigned& x, unsigned& y) {
  if (y + 1 >= m) ++x, y = 0; else ++y;
}
inline void Lst(unsigned& x, unsigned& y) {
  if (y) --y; else --x, y = m - 1;
}
inline void DFS(unsigned Dep) {
  if(Dep > m) {
    if(!Cnt) {
      List[Now] = ++Con, Find[Con] = Now;
      for (unsigned i(0), j(0); i <= m; ++i, j += 2) {
        char Cur((Now >> j) & 3);
        if(Cur) {
          if(Cur == 1) Stack[++STop] = i;
          else g[Con][Stack[STop]] = i, g[Con][i] = Stack[STop--];
        }
      }
    }
    return;
  }
  DFS(Dep + 1);
  Now ^= (1 << (Dep << 1)), ++Cnt;
  DFS(Dep + 1);
  --Cnt, Now ^= (1 << (Dep << 1));
  if(Cnt) {
    Now ^= (2 << (Dep << 1)), --Cnt;
    DFS(Dep + 1);
    ++Cnt, Now ^= (2 << (Dep << 1));
  }
}
signed main() {
  n = RD(), N = ((1 << (m = RD()) + 1) << 1), DFS(0);
  for (unsigned i(1); i <= n; ++i) scanf("%s", a[i]);
  for (unsigned i(1); (i <= n) && (!Flg); ++i) for (unsigned j(0); j < m; ++j)
    if (a[i][j] ^ '*') { Flg = 1, A = i, B = j;break; }
  if (!Flg) { printf("1\n"); return 0; } Flg = 0;
  for (unsigned i(n); i && (!Flg); --i) for (unsigned j(m - 1); ~j; --j)
    if (a[i][j] ^ '*') { Flg = 1, C = i, D = j;break; }
  if ((B == (m - 1)) || (!D)) { printf("0\n"); return 0;}
  Lst(A, B), f[1] = 1;
  unsigned Pli(A), Plj(B); Nxt(Pli, Plj), Lst(C, D); 
  for (unsigned i(A), j(B); (i ^ C) || (j ^ D); i = Pli, j = Plj, Nxt(Pli, Plj)) {
    if(a[Pli][Plj] ^ '.') {
      if(Plj) {for (unsigned k(1); k <= Con; ++k) if (f[k]) {
        unsigned Cur(Find[k]), Up(Cur & (3 << ((Plj + 1) << 1))), Lef(Cur & (3 << ((Plj << 1))));
        if(!(Up | Lef)) Tmp[List[(Cur ^ Up) ^ Lef]] += f[k];
      }}
      else for (unsigned k(1); k <= Con; ++k) if (f[k]) {
        unsigned Cur(Find[k]);
        if(!(Cur & 3)) Tmp[List[Cur << 2]] += f[k];
      }
      memcpy(f, Tmp, (Con + 1) << 3);
      memset(Tmp, 0, (Con + 1) << 3);
      continue;
    }
    if(!Plj) {
      for (unsigned k(1); k <= Con; ++k) if (f[k]) {
        unsigned Cur(Find[k]), To(Cur << 2);
        char Up(Cur & 3);
        if (Up) Tmp[List[To]] += f[k], To ^= 5;
        else To ^= 9;
        Tmp[List[To]] += f[k];
      }
    } else for (unsigned k(1); k <= Con; ++k) if (f[k]) {
      unsigned Cur(Find[k]), To(Cur);
      char Up((Cur >> ((Plj + 1) << 1)) & 3), Lef(Cur >> ((Plj << 1)) & 3);
      if(!(Up | Lef)) To ^= (9 << (Plj << 1));
      else if(a[Pli][Plj]) {
        if((!Lef) || (!Up)) Tmp[k] += f[k], To ^= (((Up + Lef) | ((Lef + Up) << 2)) << (Plj << 1));
        else {
          To ^= (Up << ((Plj + 1) << 1));
          To ^= (Lef << (Plj << 1));
          if(Lef & Up) To ^= (3 << (g[k][Plj + (Lef == 1)] << 1));
          else if(Lef == 1) continue;
        }
      }
      Tmp[List[To]] += f[k];
    }
    memcpy(f, Tmp, (Con + 1) << 3);
    memset(Tmp, 0, (Con + 1) << 3);
  }
  Nxt(C, D), Ans = ((1 << (D << 1)) | (2 << ((D + 1) << 1)));
  printf("%llu\n", f[List[Ans]]);
  return Wild_Donkey;
}
posted @ 2022-03-07 16:53  Wild_Donkey  阅读(151)  评论(0编辑  收藏  举报