[ABC295Ex] E or m 题解

状压 dp,2 hd 4 me/ng。

题意

开始你有一个全 \(0\) 矩阵,你可以随意执行如下操作:

  • 选择任意一行,将其从最左端开始的连续一段染成 \(1\)
  • 选择任意一列,将其从最上端开始的连续一段染成 \(1\)

如果一个矩阵可以由此得到,那么这个矩阵被称为好的。

现在你有一个 01? 矩阵 \(a\),你需要将所有 ? 替换为 01,问得到的矩阵中有多少个是好的。答案对 \(998244353\) 取模。

\(1\le n,m\le 18\)

思路

可以发现一个矩阵是否合法取决于对每个格子而言,其上方或左方是否有一方仍是全 \(1\) 段。

于是考虑设 \(f_{(i,j),k=0/1,s}\) 表示现在处理完了 \((i,j)\),列状态为 \(s\)(列状态指目前 \(m\) 列里每一列是否仍为全 \(1\) 段),行状态为 \(k\)(即当前行是否仍为全 \(1\) 段)。

初始状态为 \(f_{(1,0),1,\text{full}}=1\),即啥都没填的情况。

对当前状态 \(f_{(i,j),k,s}\),我们分类讨论:

格子不在行末

那么下一个格子即为 \((i,j+1)\),我们继续分类讨论:

  • \(a_{i,j+1}=0\):那么第 \(j+1\) 列的列状态则变成 \(0\),第 \(i\) 行的行状态变为 \(0\),即 \(f_{(i,j),k,s}\to f_{(i,j+1),0,s_{j+1}\gets 0}\)
  • \(a_{i,j+1}=1\):要求当前行状态与第 \(j+1\) 列的列状态不能全为 \(0\),转移方程为 \(f_{(i,j),k,s}\to f_{(i,j+1),k,s}\)
  • \(a_{i,j+1}=\text{?}\):综合上面两种情况即可。

格子在行末

可以发现这种情况相当于将 \((i,j)\) 移到了下一行的 \((i+1,0)\),重置行状态,然后按 \(j<m\) 做即可。

最终答案即为 \(\displaystyle\sum_{i}\sum_{j}f_{(n,m),i,j}\)

#include <iostream>

using namespace std;

const int kN = 19;
const int kM = 998244353;

int n, m;
char a[kN][kN];
int f[kN][kN][2][1 << kN], ans;

void A(int &f, int v) {
  if ((f += v) >= kM) {
    f -= kM;
  }
}
void E(int x, int y, int k, int s, int v) {
  if (x == n && y == m) {
    A(ans, v);
    return;
  }
  if (y == m) {
    ++x, y = 0, k = 1;
  }
  if (a[x][y + 1] != '1') {
    A(f[x][y + 1][0][s & ~(1 << y)], v);
  }
  if (a[x][y + 1] != '0' && (k || (s >> y & 1))) {
    A(f[x][y + 1][k][s], v);
  }
}

int main() {
  ios_base::sync_with_stdio(0), cin.tie(0);
  cin >> n >> m;
  for (int i = 1; i <= n; ++i) {
    cin >> a[i] + 1;
  }
  E(1, 0, 1, (1 << m) - 1, 1);
  for (int i = 1; i <= n; ++i) {
    for (int j = 1; j <= m; ++j) {
      for (int k = 0; k < 2; ++k) {
        for (int p = 0; p < (1 << m); ++p) {
          E(i, j, k, p, f[i][j][k][p]);
        }
      }
    }
  }
  cout << ans;
  return 0;
}
posted @ 2023-03-26 21:39  bykem  阅读(75)  评论(0)    收藏  举报