#include #include using namespace std; using isize = size_t; using i32 = int; using u32 = unsigned int; using i64 = long long; using u64 = unsigned long long; using i128 = __int128_t; using u128 = __uint128_t; using f64 = long double; using p2 = pair; using el = tuple; using mint = atcoder::modint998244353; void _main(); int main() { cin.tie(0); ios::sync_with_stdio(false); cout << fixed << setprecision(18); _main(); } i64 pow(i64 x, i64 n) { i64 res = 1; i64 t = x; while (n > 0) { if (n & 1) res *= t; t *= t; n >>= 1; } return res; } i64 pow(i64 x, i64 n, i64 m) { i64 res = 1; i64 t = x % m; while (n > 0) { if (n & 1) res = res * t % m; t = t * t % m; n >>= 1; } return res; } i64 N; vector> ranked_zeta(vector a) { vector> res(1 << N, vector(N + 1, 0)); for (i64 i = 0; i < 1 << N; i++) { res[i][__builtin_popcountll(i)] = a[i]; } for (i64 i = 0; i < N; i++) { for (i64 j = 0; j < 1 << N; j++) { for (i64 k = 0; k <= N; k++) { if (!(j >> i & 1)) continue; res[j][k] += res[j - (1 << i)][k]; } } } return res; } vector ranked_mobius(vector> a) { vector res(1 << N, 0); for (i64 i = 0; i < N; i++) { for (i64 j = 0; j < 1 << N; j++) { for (i64 k = 0; k <= N; k++) { if (!(j >> i & 1)) continue; a[j][k] -= a[j - (1 << i)][k]; } } } for (i64 i = 0; i < 1 << N; i++) { res[i] = a[i][__builtin_popcountll(i)]; } return res; } vector subset_convolution(vector &a, vector &b) { vector> u = ranked_zeta(a); vector> v = ranked_zeta(b); vector> s(1 << N, vector(N + 1, 0)); for (i64 i = 0; i < 1 << N; i++) { for (i64 j = 0; j <= N; j++) { for (i64 k = 0; j + k <= N; k++) { s[i][j + k] += u[i][j] * v[i][k]; } } } return ranked_mobius(s); } void _main() { i64 n, m; cin >> n >> m; vector> a(n, vector(m)); for (i64 i = 0; i < n; i++) { for (i64 j = 0; j < m; j++) { cin >> a[i][j]; } } vector col; for (i64 i = 0; i < n; i++) col.push_back(a[i][0]); sort(col.begin(), col.end()); col.erase(unique(col.begin(), col.end()), col.end()); mint ans = 0; if (n <= 18) { for (i64 x : col) { vector> s(n, vector(m)); vector v; for (i64 i = 0; i < n; i++) { for (i64 j = 0; j < m; j++) { s[i][j] = x == a[i][j]; } if (s[i][0]) v.push_back(i); } if (v.empty()) continue; vector dp(1 << v.size(), 0); dp[(1 << v.size()) - 1] = 1; for (i64 k = 1; k <= m; k++) { vector ndp(1 << v.size(), 0); for (i64 i = 0; i < 1 << v.size(); i++) { for (i64 j = 0; j < v.size(); j++) { if (!(i >> j & 1)) continue; if (k == m) { ans += dp[i]; continue; } if (s[v[j]][k]) { ndp[i] += dp[i]; } else { ndp[i ^ (1 << j)] += dp[i]; } } } swap(dp, ndp); } } } else { for (i64 x : col) { vector> s(n, vector(m)); vector v; for (i64 i = 0; i < n; i++) { for (i64 j = 0; j < m; j++) { s[i][j] = x == a[i][j]; } if (s[i][0]) v.push_back(i); } vector dp(1 << m, 0); dp[0] = 1; N = m; for (i64 i : v) { vector ndp(1 << m, 0); for (i64 j = 0; j < 1 << m; j++) { bool ok = true; i64 mx = -1; for (i64 k = 0; k < m; k++) { if (j >> k & 1) { mx = k; } } for (i64 k = 0; k < m; k++) { if (k == mx) continue; if (j >> k & 1) { ok &= s[i][k + 1]; } } if (!ok) continue; ndp[j] = 1; } dp = subset_convolution(dp, ndp); } ans += dp[(1 << m) - 1]; } } cout << ans.val() << "\n"; }