[ABC295Ex] E or m 题解
状压 dp,2 hd 4 me/ng。
题意
开始你有一个全 \(0\) 矩阵,你可以随意执行如下操作:
- 选择任意一行,将其从最左端开始的连续一段染成 \(1\)。
- 选择任意一列,将其从最上端开始的连续一段染成 \(1\)。
如果一个矩阵可以由此得到,那么这个矩阵被称为好的。
现在你有一个 01? 矩阵 \(a\),你需要将所有 ? 替换为 0 或 1,问得到的矩阵中有多少个是好的。答案对 \(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;
}

浙公网安备 33010602011771号