Given are an N \times N matrix and an integer K. The entry in the i-th row and j-th column of this matrix is denoted as a_{i, j}. This matrix contains each of 1, 2, \dots, N^2 exactly once.
Sigma can repeat the following two kinds of operation arbitrarily many times in any order.
 - Pick two integers x, y (1 \leq x < y \leq N) that satisfy a_{i, x} + a_{i, y} \leq K for all i (1 \leq i \leq N) and swap the x-th and the y-th columns.
 - Pick two integers x, y (1 \leq x < y \leq N) that satisfy a_{x, i} + a_{y, i} \leq K for all i (1 \leq i \leq N) and swap the x-th and the y-th rows.
How many matrices can he obtain by these operations? Find it modulo 998244353.

-----Constraints-----
 - 1 \leq N \leq 50
 - 1 \leq K \leq 2 \times N^2
 - a_{i, j}'s are a rearrangement of 1, 2, \dots, N^2.
 - All values in input are integers.

-----Input-----
Input is given from Standard Input in the following format:
N K
a_{1, 1} a_{1, 2} ... a_{1, N}
a_{2, 1} a_{2, 2} ... a_{2, N}
:
a_{N, 1} a_{N, 2} ... a_{N, N}

-----Output-----
Print the number of matrices Sigma can obtain modulo 998244353.

-----Sample Input-----
3 13
3 2 7
4 8 9
1 6 5

-----Sample Output-----
12

For example, Sigma can swap two columns, by setting x = 1, y = 2. After that, the resulting matrix will be:
2 3 7
8 4 9
6 1 5

After that, he can swap two row vectors by setting x = 1, y = 3, resulting in the following matrix:
6 1 5
8 4 9
2 3 7

Difficulty: MEDIUM_HARD

Solutions:
import bisect, collections, copy, heapq, itertools, math, numpy as np, string, sys

def I():
	return int(sys.stdin.readline().rstrip())

def LI():
	return list(map(int, sys.stdin.readline().rstrip().split()))

def S():
	return sys.stdin.readline().rstrip()

def LS():
	return list(sys.stdin.readline().rstrip().split())

class UnionFind:

	def __init__(self, n):
		self.n = n
		self.parents = [-1] * n

	def find(self, x):
		if self.parents[x] < 0:
			return x
		else:
			self.parents[x] = self.find(self.parents[x])
			return self.parents[x]

	def union(self, x, y):
		x = self.find(x)
		y = self.find(y)
		if x == y:
			return
		if self.parents[x] > self.parents[y]:
			(x, y) = (y, x)
		self.parents[x] += self.parents[y]
		self.parents[y] = x

	def size(self, x):
		return -self.parents[self.find(x)]

	def same(self, x, y):
		return self.find(x) == self.find(y)
(N, K) = LI()
a = np.array([LI() for _ in range(N)])
(gyou, retu) = ([], [])
ans = 1
for (i, j) in itertools.combinations(list(range(N)), 2):
	if (a[i, :] + a[j, :] <= K).all():
		gyou.append((i, j))
uf_gyou = UnionFind(N)
for (x, y) in gyou:
	uf_gyou.union(x, y)
for x in uf_gyou.parents:
	if x <= -2:
		for i in range(2, abs(x) + 1):
			ans *= i
			ans %= 998244353
a = a.T
for (i, j) in itertools.combinations(list(range(N)), 2):
	if (a[i, :] + a[j, :] <= K).all():
		retu.append((i, j))
uf_retu = UnionFind(N)
for (x, y) in retu:
	uf_retu.union(x, y)
for x in uf_retu.parents:
	if x <= -2:
		for i in range(2, abs(x) + 1):
			ans *= i
			ans %= 998244353
print(ans)


import math
(N, K) = map(int, input().split())
A = [list(map(int, input().split())) for _ in range(N)]
mod = 998244353

class DSU:

	def __init__(self, n):
		self.uf = {i: i for i in range(n)}
		self.rank = {i: 1 for i in range(n)}

	def find(self, x):
		if self.uf[x] != x:
			self.uf[x] = self.find(self.uf[x])
		return self.uf[x]

	def union(self, x, y):
		(px, py) = (self.find(x), self.find(y))
		if px != py:
			if self.rank[px] > self.rank[py]:
				(px, py) = (py, px)
			self.rank[py] += self.rank[px]
			self.uf[px] = py
(row, col) = (DSU(N), DSU(N))
for i in range(N):
	for j in range(i + 1, N):
		if all((a + b <= K for (a, b) in zip(A[i], A[j]))):
			row.union(i, j)
B = list(zip(*A))
for i in range(N):
	for j in range(i + 1, N):
		if all((a + b <= K for (a, b) in zip(B[i], B[j]))):
			col.union(i, j)
ans = 1
for x in set((row.find(i) for i in range(N))):
	ans *= math.factorial(row.rank[x])
	ans %= mod
for x in set((col.find(i) for i in range(N))):
	ans *= math.factorial(col.rank[x])
	ans %= mod
print(ans)


import sys
import math

def input():
	return sys.stdin.readline().rstrip()

class UnionFind:

	def __init__(self, n):
		self.n = n
		self.parents = [-1] * n

	def find(self, x):
		if self.parents[x] < 0:
			return x
		else:
			self.parents[x] = self.find(self.parents[x])
			return self.parents[x]

	def union(self, x, y):
		x = self.find(x)
		y = self.find(y)
		if x == y:
			return
		if self.parents[x] > self.parents[y]:
			(x, y) = (y, x)
		self.parents[x] += self.parents[y]
		self.parents[y] = x

	def size(self, x):
		return -self.parents[self.find(x)]

	def same(self, x, y):
		return self.find(x) == self.find(y)

	def members(self, x):
		root = self.find(x)
		return [i for i in range(self.n) if self.find(i) == root]

	def roots(self):
		return [i for (i, x) in enumerate(self.parents) if x < 0]

	def group_count(self):
		return len(self.roots())

	def all_group_members(self):
		return {r: self.members(r) for r in self.roots()}

	def __str__(self):
		return '\n'.join(('{}: {}'.format(r, self.members(r)) for r in self.roots()))

	def all_family_count(self):
		return [-min(0, x) for (i, x) in enumerate(self.parents)]

def main():
	(N, K) = list(map(int, input().split()))
	mod = 998244353
	A = [[] for _ in range(N)]
	for i in range(N):
		a = list(map(int, input().split()))
		A[i] = a
	row = UnionFind(N)
	column = UnionFind(N)
	for i in range(N - 1):
		for j in range(i + 1, N):
			for w in range(N):
				if A[i][w] + A[j][w] > K:
					break
			else:
				row.union(i, j)
	for i in range(N - 1):
		for j in range(i + 1, N):
			for w in range(N):
				if A[w][i] + A[w][j] > K:
					break
			else:
				column.union(i, j)
	root_row = row.all_family_count()
	ans_row = 1
	for i in range(N):
		k = root_row[i]
		ans_row *= math.factorial(k)
	root_column = column.all_family_count()
	ans_column = 1
	for i in range(N):
		k = root_column[i]
		ans_column *= math.factorial(k)
	print(ans_column * ans_row % mod)

def __starting_point():
	main()
__starting_point()


def MI():
	return map(int, input().split())
(n, k) = MI()
mod = 998244353
framod = [1]

def framod_calc(n, mod, a=1):
	for i in range(1, n + 1):
		a = a * i % mod
		framod.append(a)
framod_calc(n + 1, mod)

def permmod(n, k, mod):
	if n < k:
		return 0
	a = framod[n]
	c = framod[n - k]
	return a * pow(c, mod - 2, mod) % mod
mat = []
matinv = [[0] * n for _ in range(n)]
for i in range(n):
	mat.append(list(MI()))
for i in range(n):
	for j in range(n):
		matinv[i][j] = mat[j][i]
esrow = []
escol = []
for i in range(n):
	for j in range(i + 1, n):
		flg = True
		for p in range(n):
			if mat[i][p] + mat[j][p] > k:
				flg = False
				break
		if flg:
			esrow.append((i, j))
for i in range(n):
	for j in range(i + 1, n):
		flg = True
		for p in range(n):
			if matinv[i][p] + matinv[j][p] > k:
				flg = False
				break
		if flg:
			escol.append((i, j))

class UnionFind(object):

	def __init__(self, n=1):
		self.n = n
		self.par = [i for i in range(n)]
		self.rank = [0 for _ in range(n)]
		self.sizebox = [1] * n

	def find(self, x):
		if self.par[x] == x:
			return x
		else:
			self.par[x] = self.find(self.par[x])
			return self.par[x]

	def union(self, x, y):
		x = self.find(x)
		y = self.find(y)
		if x != y:
			if self.rank[x] < self.rank[y]:
				(x, y) = (y, x)
			if self.rank[x] == self.rank[y]:
				self.rank[x] += 1
			self.par[y] = x
			self.sizebox[x] += self.sizebox[y]
			self.sizebox[y] = 0

	def same(self, x, y):
		return self.find(x) == self.find(y)

	def size(self, x):
		x = self.find(x)
		return self.sizebox[x]

	def allfind(self):
		for i in range(self.n):
			self.find(i)
ufr = UnionFind(n)
ufc = UnionFind(n)
for (i, j) in esrow:
	ufr.union(i, j)
for (i, j) in escol:
	ufc.union(i, j)
ufr.allfind()
ufc.allfind()
from collections import Counter
cpar = Counter(ufc.par)
rpar = Counter(ufr.par)
ans = 1
for k in cpar.keys():
	x = cpar[k]
	ans *= permmod(x, x, mod)
	ans %= mod
for k in rpar.keys():
	x = rpar[k]
	ans *= permmod(x, x, mod)
	ans %= mod
print(ans % mod)


(n, k) = list(map(int, input().split()))
m = 998244353
P = [-1] * n

def find(x):
	if P[x] < 0:
		return x
	P[x] = find(P[x])
	return P[x]

def unite(x, y):
	x = find(x)
	y = find(y)
	if x == y:
		return
	if P[x] > P[y]:
		P[y] += P[x]
		P[x] = y
	else:
		P[x] += P[y]
		P[y] = x

def f(n):
	a = 1
	while n > 0:
		a *= n
		n -= 1
	return a
A = [[int(c) for c in input().split()] for r in range(n)]
for r1 in range(n):
	for r2 in range(r1 + 1, n):
		for c in range(n):
			if A[r1][c] + A[r2][c] > k:
				break
		else:
			unite(r1, r2)
a = 1
for r in range(n):
	if P[r] < 0:
		a = a * f(-P[r]) % m
P = [-1] * n
for c1 in range(n):
	for c2 in range(c1 + 1, n):
		for r in range(n):
			if A[r][c1] + A[r][c2] > k:
				break
		else:
			unite(c1, c2)
for c in range(n):
	if P[c] < 0:
		a = a * f(-P[c]) % m
print(a)


def main():
	import sys
	from collections import Counter
	from math import factorial
	input = sys.stdin.readline
	MOD = 998244353
	(N, K) = [int(x) for x in input().strip().split()]
	A = [0 for n in range(N)]
	for n in range(N):
		A[n] = [int(x) for x in input().strip().split()]
	ans = 1

	def find(x):
		if c[x] == x:
			return x
		c[x] = find(c[x])
		return c[x]
	c = [n for n in range(N)]
	for i in range(N - 1):
		for j in range(i + 1, N):
			for n in range(N):
				if A[n][i] + A[n][j] > K:
					break
			else:
				if c[j] > c[i]:
					c[j] = c[i]
				else:
					c[i] = c[j]
	for i in range(N):
		find(i)
	for d in Counter(c).values():
		ans = ans * factorial(d) % MOD
	c = [n for n in range(N)]
	for i in range(N - 1):
		for j in range(i + 1, N):
			for n in range(N):
				if A[i][n] + A[j][n] > K:
					break
			else:
				if c[j] > c[i]:
					c[j] = c[i]
				else:
					c[i] = c[j]
	for i in range(N):
		find(i)
	for d in Counter(c).values():
		ans = ans * factorial(d) % MOD
	print(ans)

def __starting_point():
	main()
__starting_point()


class dsu:
	__slots__ = ['_n', 'parent_or_size']

	def __init__(self, n):
		self._n = n
		self.parent_or_size = [-1] * n

	def merge(self, a, b):
		x = self.leader(a)
		y = self.leader(b)
		if x == y:
			return x
		else:
			if self.parent_or_size[y] < self.parent_or_size[x]:
				(x, y) = (y, x)
			self.parent_or_size[x] += self.parent_or_size[y]
			self.parent_or_size[y] = x

	def same(self, a, b):
		return self.leader(a) == self.leader(b)

	def leader(self, a):
		path = []
		while self.parent_or_size[a] >= 0:
			path.append(a)
			a = self.parent_or_size[a]
		for child in path:
			self.parent_or_size[child] = a
		return a

	def size(self, a):
		return -self.parent_or_size[self.leader(a)]

	def groups(self):
		anc2mem = dict()
		for i in range(self._n):
			anc = self.leader(i)
			if anc in anc2mem:
				anc2mem[anc].append(i)
			else:
				anc2mem[anc] = [i]
		return list(anc2mem.values())
MOD = 998244353
table_len = 110
fac = [1, 1]
for i in range(2, table_len):
	fac.append(fac[-1] * i % MOD)
finv = [0] * table_len
finv[-1] = pow(fac[-1], MOD - 2, MOD)
for i in range(table_len - 1, 0, -1):
	finv[i - 1] = finv[i] * i % MOD
(N, K) = list(map(int, input().split()))
As = [list(map(int, input().split())) for _ in range(N)]

def solve_row(mat):
	uf = dsu(N)
	for i in range(N):
		for j in range(i + 1, N):
			for k in range(N):
				if mat[i][k] + mat[j][k] > K:
					break
			else:
				uf.merge(i, j)
	groups = uf.groups()
	ret = 1
	for g in groups:
		ret *= fac[len(g)]
		ret %= MOD
	return ret
tAs = list(map(list, list(zip(*As))))
print(solve_row(As) * solve_row(tAs) % MOD)


import sys

def input():
	return sys.stdin.readline()[:-1]

class UnionFind:

	def __init__(self, size):
		self.table = [-1 for _ in range(size)]
		self.size = [1 for _ in range(size)]

	def find(self, x):
		while self.table[x] >= 0:
			if self.table[self.table[x]] >= 0:
				self.table[x] = self.table[self.table[x]]
			x = self.table[x]
		return x

	def same(self, x, y):
		return self.find(x) == self.find(y)

	def unite(self, x, y):
		s1 = self.find(x)
		s2 = self.find(y)
		if s1 != s2:
			r1 = self.table[s1]
			r2 = self.table[s2]
			if r1 <= r2:
				self.table[s2] = s1
				if r1 == r2:
					self.table[s1] -= 1
				self.size[s1] += self.size[s2]
				self.size[s2] = 0
			else:
				self.table[s1] = s2
				self.size[s2] += self.size[s1]
				self.size[s1] = 0
		return
MOD = 998244353
(n, k) = map(int, input().split())
a = [list(map(int, input().split())) for _ in range(n)]
ans = 1
uf_t = UnionFind(n)
uf_y = UnionFind(n)
for s in range(1, n):
	for t in range(s):
		(ok_y, ok_t) = (True, True)
		for i in range(n):
			if a[s][i] + a[t][i] > k:
				ok_y = False
			if a[i][s] + a[i][t] > k:
				ok_t = False
		if ok_y:
			uf_y.unite(s, t)
		if ok_t:
			uf_t.unite(s, t)
fact = [1 for _ in range(51)]
for i in range(1, 51):
	fact[i] = fact[i - 1] * i % MOD
for i in range(n):
	if uf_t.size[i] > 0:
		ans *= fact[uf_t.size[i]]
		ans %= MOD
	if uf_y.size[i] > 0:
		ans *= fact[uf_y.size[i]]
		ans %= MOD
print(ans)


class UnionFind(object):

	def __init__(self, n=1):
		self.par = [i for i in range(n)]
		self.rank = [0 for _ in range(n)]
		self.size = [1 for _ in range(n)]

	def find(self, x):
		if self.par[x] == x:
			return x
		else:
			self.par[x] = self.find(self.par[x])
			return self.par[x]

	def union(self, x, y):
		x = self.find(x)
		y = self.find(y)
		if x != y:
			if self.rank[x] < self.rank[y]:
				(x, y) = (y, x)
			if self.rank[x] == self.rank[y]:
				self.rank[x] += 1
			self.par[y] = x
			self.size[x] += self.size[y]

	def is_same(self, x, y):
		return self.find(x) == self.find(y)

	def get_size(self, x):
		x = self.find(x)
		return self.size[x]
(N, K) = map(int, input().split())
MOD = 998244353
fac = [-1] * (N + 1)
fac[0] = 1
fac[1] = 1
finv = [-1] * (N + 1)
finv[0] = 1
finv[1] = 1
inv = [-1] * (N + 1)
inv[0] = 0
inv[1] = 1
for i in range(2, N + 1):
	fac[i] = fac[i - 1] * i % MOD
	inv[i] = MOD - inv[MOD % i] * (MOD // i) % MOD
	finv[i] = finv[i - 1] * inv[i] % MOD
import numpy as np
A = np.array([list(map(int, input().split())) for _ in range(N)])
uf0 = UnionFind(N)
for i in range(N):
	for j in range(N):
		if i == j:
			continue
		for k in range(N):
			if A[i, k] + A[j, k] > K:
				break
		else:
			uf0.union(i, j)
par0 = set([])
for i in range(N):
	par = uf0.find(i)
	par0.add(par)
X = 1
for x in par0:
	temp = uf0.get_size(x)
	X = X * fac[temp] % MOD
A = A.T
uf1 = UnionFind(N)
for i in range(N):
	for j in range(N):
		if i == j:
			continue
		for k in range(N):
			if A[i, k] + A[j, k] > K:
				break
		else:
			uf1.union(i, j)
par1 = set([])
for i in range(N):
	par = uf1.find(i)
	par1.add(par)
Y = 1
for y in par1:
	temp = uf1.get_size(y)
	Y = Y * fac[temp] % MOD
ans = X * Y % MOD
print(ans)


import sys
from collections import deque, Counter
from heapq import heappop, heappush
from bisect import bisect_right
from itertools import accumulate
sys.setrecursionlimit(10 ** 6)
INF = 10 ** 12
m = 998244353

class UnionFind:

	def __init__(self, n):
		self.parents = list(range(n))
		self.size = [1] * n

	def find(self, x):
		if self.parents[x] == x:
			return x
		else:
			self.parents[x] = self.find(self.parents[x])
			return self.parents[x]

	def union(self, x, y):
		x = self.find(x)
		y = self.find(y)
		if x != y:
			if self.size[x] < self.size[y]:
				(x, y) = (y, x)
			self.parents[y] = x
			self.size[x] += self.size[y]

def main():
	(N, K) = list(map(int, input().split()))
	A = [list(map(int, input().split())) for _ in range(N)]
	ans = 1
	p = 998244353
	a = [None] * (N + 1)
	inva = [None] * (N + 1)
	a[0] = 1
	for i in range(1, N + 1):
		a[i] = i * a[i - 1] % p
	inva[N] = pow(a[N], p - 2, p)
	for i in range(N):
		inva[N - i - 1] = inva[N - i] * (N - i) % p
	rowuf = UnionFind(N)
	for x in range(N):
		for y in range(x + 1, N):
			for i in range(N):
				if A[i][x] + A[i][y] > K:
					break
			else:
				rowuf.union(x, y)
	rows = []
	for i in range(N):
		rows.append(rowuf.find(i))
	for l in list(Counter(rows).values()):
		ans *= a[l]
		ans %= p
	coluf = UnionFind(N)
	for x in range(N):
		for y in range(x + 1, N):
			for i in range(N):
				if A[x][i] + A[y][i] > K:
					break
			else:
				coluf.union(x, y)
	cols = []
	for i in range(N):
		cols.append(coluf.find(i))
	for l in list(Counter(cols).values()):
		ans *= a[l]
		ans %= p
	print(ans)

def __starting_point():
	main()
__starting_point()


class union_find:

	def __init__(self, n):
		self.par = [-1] * n

	def find(self, x):
		if self.par[x] < 0:
			return x
		else:
			self.par[x] = self.find(self.par[x])
			return self.par[x]

	def unite(self, x, y):
		(x, y) = (self.find(x), self.find(y))
		if x != y:
			if x > y:
				(x, y) = (y, x)
			self.par[x] += self.par[y]
			self.par[y] = x

	def same(self, x, y):
		return self.find(x) == self.find(y)

	def size(self, x):
		return -self.par[self.find(x)]
import itertools
mod = 998244353

def main():
	(n, k) = map(int, input().split())
	factorial = [1]
	for i in range(1, n + 1):
		factorial.append(factorial[i - 1] * i % mod)
	a = []
	for i in range(n):
		a.append(list(map(int, input().split())))
	gyou = union_find(n)
	for (x, y) in itertools.combinations(range(n), 2):
		flag = True
		for i in range(n):
			if a[x][i] + a[y][i] > k:
				flag = False
				break
		if flag:
			gyou.unite(x, y)
	retu = union_find(n)
	for (x, y) in itertools.combinations(range(n), 2):
		flag = True
		for i in range(n):
			if a[i][x] + a[i][y] > k:
				flag = False
				break
		if flag:
			retu.unite(x, y)
	ans = 1
	for i in range(len(retu.par)):
		if retu.par[i] < 0:
			ans *= factorial[-retu.par[i]]
		ans %= mod
	for i in range(len(gyou.par)):
		if gyou.par[i] < 0:
			ans *= factorial[-gyou.par[i]]
		ans %= mod
	print(ans)

def __starting_point():
	main()
__starting_point()


class DSU:

	def __init__(self, N):
		self.parents = list(range(N))
		self.size = [1] * N

	def find(self, x):
		root = x
		while self.parents[root] != root:
			root = self.parents[root]
		while x != root:
			nextx = self.parents[x]
			self.parents[x] = root
			x = nextx
		return root

	def union(self, x, y):
		(xr, yr) = (self.find(x), self.find(y))
		if xr == yr:
			return False
		if self.size[xr] > self.size[yr]:
			(xr, yr) = (yr, xr)
		self.parents[xr] = yr
		self.size[xr] += self.size[yr]
		self.size[yr] = self.size[xr]
		return True

	def isConnected(self, x, y):
		return self.find(x) == self.find(y)

	def getSize(self, x):
		return self.size[self.find(x)]
from math import factorial as f
mod = 998244353
(n, K) = list(map(int, input().split()))
arr = []
for _ in range(n):
	arr.append(list(map(int, input().split())))
row_dsu = DSU(n)
col_dsu = DSU(n)
for i in range(n):
	for j in range(i):
		if all((arr[i][k] + arr[j][k] <= K for k in range(n))):
			row_dsu.union(i, j)
		if all((arr[k][i] + arr[k][j] <= K for k in range(n))):
			col_dsu.union(i, j)
row_set = set()
col_set = set()
for i in range(n):
	row_set.add(row_dsu.find(i))
	col_set.add(col_dsu.find(i))
row_prod = col_prod = 1
for ele in row_set:
	row_prod *= f(row_dsu.size[ele])
for ele in col_set:
	col_prod *= f(col_dsu.size[ele])
print(row_prod * col_prod % mod)


import sys
from math import factorial
sys.setrecursionlimit(10 ** 9)
read = lambda : sys.stdin.readline()

def read_ints():
	return list(map(int, read().split()))

def read_intgrid(h):
	return list((list(map(int, read().split())) for i in range(h)))

class UnionFind:

	def __init__(self, n):
		self.parents = [-1] * n
		self.rank = [0] * n
		self.group_count = n
		self.n = n

	def find(self, x):
		if self.parents[x] < 0:
			return x
		else:
			self.parents[x] = self.find(self.parents[x])
			return self.parents[x]

	def unite(self, x, y):
		x = self.find(x)
		y = self.find(y)
		if x == y:
			return
		if self.rank[x] == self.rank[y]:
			self.parents[x] += self.parents[y]
			self.parents[y] = x
			self.rank[x] += 1
		elif self.rank[x] > self.rank[y]:
			self.parents[x] += self.parents[y]
			self.parents[y] = x
		else:
			self.parents[y] += self.parents[x]
			self.parents[x] = y
		self.group_count -= 1

	def same(self, x, y):
		return self.find(x) == self.find(y)

	def get_group_member_list(self, x):
		x = self.find(x)
		return [i for i in range(self.n) if self.find(i) == x]

	def get_group_member_count(self, x):
		x = self.find(x)
		return -self.parents[x]

	def get_all_groups(self):
		return {idx: -n for (idx, n) in enumerate(self.parents) if n < 0}

def f(x):
	tmp = 1
	while x > 0:
		tmp *= x
		x -= 1
	return tmp

def main():
	(n, k) = map(int, input().split())
	A = read_intgrid(n)
	mod = 998244353
	ans = 1
	uf1 = UnionFind(n)
	uf2 = UnionFind(n)
	for i in range(n - 1):
		for j in range(i + 1, n):
			if all((A[i][c] + A[j][c] <= k for c in range(n))):
				uf1.unite(i, j)
			if all((A[c][i] + A[c][j] <= k for c in range(n))):
				uf2.unite(i, j)
	for (k, v) in uf1.get_all_groups().items():
		ans *= f(v)
		ans %= mod
	for (k, v) in uf2.get_all_groups().items():
		ans *= f(v)
		ans %= mod
	return print(ans)

def __starting_point():
	main()
__starting_point()


class UnionFind:

	def __init__(self, n):
		self.n = n
		self.p = [-1] * n

	def leader(self, a):
		while self.p[a] >= 0:
			a = self.p[a]
		return a

	def merge(self, a, b):
		x = self.leader(a)
		y = self.leader(b)
		if x == y:
			return x
		if self.p[x] > self.p[y]:
			(x, y) = (y, x)
		self.p[x] += self.p[y]
		self.p[y] = x
		return x
f = lambda : map(int, input().split())
(n, k) = f()
A = [[*f()] for _ in range(n)]
(ufh, ufw) = (UnionFind(n), UnionFind(n))
for y in range(n):
	for x in range(y):
		if all((A[h][x] + A[h][y] <= k for h in range(n))):
			ufw.merge(x, y)
		if all((A[x][w] + A[y][w] <= k for w in range(n))):
			ufh.merge(x, y)
M = 998244353
F = [1]
for i in range(1, 51):
	F += [i * F[-1] % M]
c = 1
for i in range(n):
	if ufh.p[i] < 0:
		c = c * F[-ufh.p[i]] % M
	if ufw.p[i] < 0:
		c = c * F[-ufw.p[i]] % M
print(c)


from collections import defaultdict

class UnionFind:

	def __init__(self, n):
		self.n = n
		self.parents = [-1] * n

	def find(self, x):
		if self.parents[x] < 0:
			return x
		else:
			self.parents[x] = self.find(self.parents[x])
			return self.parents[x]

	def union(self, x, y):
		x = self.find(x)
		y = self.find(y)
		if x == y:
			return
		if self.parents[x] > self.parents[y]:
			(x, y) = (y, x)
		self.parents[x] += self.parents[y]
		self.parents[y] = x

	def size(self, x):
		return -self.parents[self.find(x)]

	def same(self, x, y):
		return self.find(x) == self.find(y)

	def members(self, x):
		root = self.find(x)
		return [i for i in range(self.n) if self.find(i) == root]

	def roots(self):
		return [i for (i, x) in enumerate(self.parents) if x < 0]

	def group_count(self):
		return len(self.roots())

	def all_group_members(self):
		group_members = defaultdict(list)
		for member in range(self.n):
			group_members[self.find(member)].append(member)
		return group_members

	def __str__(self):
		return '\n'.join((f'{r}: {m}' for (r, m) in self.all_group_members().items()))
import itertools
(N, K) = map(int, input().split())
l = [list(map(int, input().split())) for i in range(N)]
a = UnionFind(N)
for v in itertools.combinations(range(N), 2):
	for i in range(N):
		if l[i][v[0]] + l[i][v[1]] > K:
			break
		if i == N - 1:
			a.union(v[0], v[1])
b = UnionFind(N)
for v in itertools.combinations(range(N), 2):
	for i in range(N):
		if l[v[0]][i] + l[v[1]][i] > K:
			break
		if i == N - 1:
			b.union(v[0], v[1])
ans = 1
for x in a.roots():
	for i in range(1, a.size(x) + 1):
		ans = ans * i % 998244353
for x in b.roots():
	for i in range(1, b.size(x) + 1):
		ans = ans * i % 998244353
print(ans)


(n, k) = map(int, input().split())
a = [list(map(int, input().split())) for i in range(n)]
retu = []
gyou = []
for i in range(n - 1):
	for j in range(i + 1, n):
		for p in range(n):
			if a[i][p] + a[j][p] > k:
				break
		else:
			gyou.append([i, j])
		for q in range(n):
			if a[q][i] + a[q][j] > k:
				break
		else:
			retu.append([i, j])
from collections import defaultdict

class UnionFind:

	def __init__(self, n):
		self.n = n
		self.parents = [-1] * n

	def find(self, x):
		if self.parents[x] < 0:
			return x
		else:
			self.parents[x] = self.find(self.parents[x])
			return self.parents[x]

	def union(self, x, y):
		x = self.find(x)
		y = self.find(y)
		if x == y:
			return
		if self.parents[x] > self.parents[y]:
			(x, y) = (y, x)
		self.parents[x] += self.parents[y]
		self.parents[y] = x

	def size(self, x):
		return -self.parents[self.find(x)]

	def same(self, x, y):
		return self.find(x) == self.find(y)

	def members(self, x):
		root = self.find(x)
		return [i for i in range(self.n) if self.find(i) == root]

	def roots(self):
		return [i for (i, x) in enumerate(self.parents) if x < 0]

	def group_count(self):
		return len(self.roots())

	def all_group_members(self):
		group_members = defaultdict(list)
		for member in range(self.n):
			group_members[self.find(member)].append(member)
		return group_members

	def __str__(self):
		return '\n'.join((f'{r}: {m}' for (r, m) in self.all_group_members().items()))
retuuf = UnionFind(n)
gyouuf = UnionFind(n)
for i in retu:
	retuuf.union(i[0], i[1])
for i in gyou:
	gyouuf.union(i[0], i[1])
gyouans = 1
retuans = 1
retulist = list(retuuf.all_group_members().values())
gyoulist = list(gyouuf.all_group_members().values())
import math
for i in retulist:
	retuans *= math.factorial(len(i))
for i in gyoulist:
	gyouans *= math.factorial(len(i))
print(retuans * gyouans % 998244353)


import math
(N, K) = list(map(int, input().split()))
A = []
mod = 998244353
for _ in range(N):
	A.append(list(map(int, input().split())))

def find(x, uf):
	if uf[x] != x:
		uf[x] = find(uf[x], uf)
	return uf[x]
uf1 = {i: i for i in range(N)}
uf2 = {i: i for i in range(N)}
rank1 = {i: 1 for i in range(N)}
rank2 = {i: 1 for i in range(N)}
for i in range(N):
	for j in range(i + 1, N):
		if all((a + b <= K for (a, b) in zip(A[i], A[j]))):
			(px, py) = (find(i, uf1), find(j, uf1))
			if px != py:
				if rank1[px] > rank1[py]:
					(px, py) = (py, px)
				rank1[py] += rank1[px]
				uf1[px] = py
B = list(zip(*A))
for i in range(N):
	for j in range(i + 1, N):
		if all((a + b <= K for (a, b) in zip(B[i], B[j]))):
			(px, py) = (find(i, uf2), find(j, uf2))
			if px != py:
				if rank2[px] > rank2[py]:
					(px, py) = (py, px)
				rank2[py] += rank2[px]
				uf2[px] = py
ans = 1
for x in set((find(i, uf1) for i in range(N))):
	ans *= math.factorial(rank1[x])
	ans %= mod
for x in set((find(i, uf2) for i in range(N))):
	ans *= math.factorial(rank2[x])
	ans %= mod
print(ans)


import bisect, copy, heapq, math, sys
from collections import *
from functools import lru_cache
from itertools import accumulate, combinations, permutations, product

def input():
	return sys.stdin.readline()[:-1]

def ruiseki(lst):
	return [0] + list(accumulate(lst))

def celi(a, b):
	return -(-a // b)
sys.setrecursionlimit(5000000)
mod = 998244353
al = [chr(ord('a') + i) for i in range(26)]
direction = [[1, 0], [0, 1], [-1, 0], [0, -1]]
(n, k) = map(int, input().split())
a = [list(map(int, input().split())) for i in range(n)]

class WeightedUnionFind:

	def __init__(self, n):
		self.par = [i for i in range(n + 1)]
		self.rank = [0] * (n + 1)
		self.weight = [0] * (n + 1)
		self.sizes = [1] * (n + 1)

	def find(self, x):
		if self.par[x] == x:
			return x
		else:
			y = self.find(self.par[x])
			self.weight[x] += self.weight[self.par[x]]
			self.par[x] = y
			return y

	def union(self, x, y, w=1):
		rx = self.find(x)
		ry = self.find(y)
		if self.rank[rx] < self.rank[ry]:
			self.sizes[ry] += self.size(rx)
			self.par[rx] = ry
			self.weight[rx] = w - self.weight[x] + self.weight[y]
		else:
			self.sizes[rx] += self.size(ry)
			self.par[ry] = rx
			self.weight[ry] = -w - self.weight[y] + self.weight[x]
			if self.rank[rx] == self.rank[ry]:
				self.rank[rx] += 1

	def same(self, x, y):
		return self.find(x) == self.find(y)

	def diff(self, x, y):
		return self.weight[x] - self.weight[y]

	def size(self, x):
		return self.sizes[self.find(x)]
retsu = WeightedUnionFind(n)
gyo = WeightedUnionFind(n)
for i in range(n):
	for j in range(i + 1, n):
		for l in range(n):
			if a[l][i] + a[l][j] > k:
				break
		else:
			if not retsu.same(i, j):
				retsu.union(i, j)
for i in range(n):
	for j in range(i + 1, n):
		for l in range(n):
			if a[i][l] + a[j][l] > k:
				break
		else:
			if not gyo.same(i, j):
				gyo.union(i, j)
ans = 1

def keisan(ans, m):
	for i in range(m):
		ans *= i + 1
		ans %= mod
	return ans
for i in range(n):
	if i == retsu.find(i):
		ans = keisan(ans, retsu.size(i))
	if i == gyo.find(i):
		ans = keisan(ans, gyo.size(i))
print(ans)


from math import factorial

class UnionFind:

	def __init__(self, n):
		self.n = n
		self.parents = [-1] * n

	def find(self, x):
		if self.parents[x] < 0:
			return x
		else:
			self.parents[x] = self.find(self.parents[x])
			return self.parents[x]

	def union(self, x, y):
		x = self.find(x)
		y = self.find(y)
		if x == y:
			return
		if self.parents[x] > self.parents[y]:
			(x, y) = (y, x)
		self.parents[x] += self.parents[y]
		self.parents[y] = x

	def size(self, x):
		return -self.parents[self.find(x)]
(n, k) = map(int, input().split())
a = [list(map(int, input().split())) for _ in range(n)]
mod = 998244353
ans = 1
flag = 1
uf = UnionFind(n)
for r1 in range(n):
	for r2 in range(r1 + 1, n):
		for c in range(n):
			flag = 1
			if a[r1][c] + a[r2][c] > k:
				flag = 0
				break
		if flag:
			uf.union(r1, r2)
for i in range(n):
	if uf.parents[i] < 0:
		ans *= factorial(uf.size(i))
		ans %= mod
uf = UnionFind(n)
for c1 in range(n):
	for c2 in range(c1 + 1, n):
		for r in range(n):
			flag = 1
			if a[r][c1] + a[r][c2] > k:
				flag = 0
				break
		if flag:
			uf.union(c1, c2)
for i in range(n):
	if uf.parents[i] < 0:
		ans *= factorial(uf.size(i))
		ans %= mod
print(ans)


(n, k) = list(map(int, input().split()))
A = [list(map(int, input().split())) for _ in range(n)]

def Find(x, par):
	if par[x] < 0:
		return x
	else:
		par[x] = Find(par[x], par)
		return par[x]

def Unite(x, y, par, rank):
	x = Find(x, par)
	y = Find(y, par)
	if x != y:
		if rank[x] < rank[y]:
			par[y] += par[x]
			par[x] = y
		else:
			par[x] += par[y]
			par[y] = x
			if rank[x] == rank[y]:
				rank[x] += 1

def Same(x, y, par):
	return Find(x, par) == Find(y, par)

def Size(x, par):
	return -par[Find(x, par)]
par1 = [-1] * n
rank1 = [0] * n
for x in range(n):
	for y in range(n):
		for i in range(n):
			if A[x][i] + A[y][i] > k:
				break
		else:
			Unite(x, y, par1, rank1)
par2 = [-1] * n
rank2 = [0] * n
for x in range(n):
	for y in range(n):
		for i in range(n):
			if A[i][x] + A[i][y] > k:
				break
		else:
			Unite(x, y, par2, rank2)
N = 1000
mod = 998244353
fac = [1] * (N + 1)
finv = [1] * (N + 1)
for i in range(N):
	fac[i + 1] = fac[i] * (i + 1) % mod
finv[-1] = pow(fac[-1], mod - 2, mod)
for i in reversed(list(range(N))):
	finv[i] = finv[i + 1] * (i + 1) % mod
ans = 1
for i in par1:
	if i < 0:
		ans *= fac[-i]
		ans %= mod
for i in par2:
	if i < 0:
		ans *= fac[-i]
		ans %= mod
print(ans)


from collections import defaultdict
import math
import numpy as np
(n, k) = map(int, input().split())
a_lists = [list(map(int, input().split())) for _ in range(n)]
mod = 998244353
a_lists = np.array(a_lists)

class UnionFind:

	def __init__(self, n):
		self.n = n
		self.parents = [-1] * n

	def find(self, x):
		if self.parents[x] < 0:
			return x
		else:
			self.parents[x] = self.find(self.parents[x])
			return self.parents[x]

	def union(self, x, y):
		x = self.find(x)
		y = self.find(y)
		if x == y:
			return
		if self.parents[x] > self.parents[y]:
			(x, y) = (y, x)
		self.parents[x] += self.parents[y]
		self.parents[y] = x

	def size(self, x):
		return -self.parents[self.find(x)]

	def same(self, x, y):
		return self.find(x) == self.find(y)

	def members(self, x):
		root = self.find(x)
		return [i for i in range(self.n) if self.find(i) == root]

	def roots(self):
		return [i for (i, x) in enumerate(self.parents) if x < 0]

	def group_count(self):
		return len(self.roots())

	def all_group_members(self):
		group_members = defaultdict(list)
		for member in range(self.n):
			group_members[self.find(member)].append(member)
		return group_members

	def __str__(self):
		return '\n'.join((f'{r}: {m}' for (r, m) in self.all_group_members().items()))

def count_point(np_array):
	uf = UnionFind(n)
	for i in range(n):
		a = np_array[i]
		for j in range(i, n):
			b = np_array[j]
			if np.count_nonzero(a + b <= k) == n:
				uf.union(i, j)
	point = 1
	for (i, j) in uf.all_group_members().items():
		point *= math.factorial(len(j))
		point %= mod
	return point
ans = count_point(a_lists)
a_lists_T = a_lists.T
ans *= count_point(a_lists_T)
ans %= mod
print(ans)


(N, K) = map(int, input().split())
data = [list(map(int, input().split())) for i in range(N)]
import math
ans1 = []
for i in range(N - 1):
	for j in range(i + 1, N):
		m = 0
		for k in range(N):
			if data[i][k] + data[j][k] > K:
				m += 1
				break
		if m == 0:
			ans1.append([i, j])
ans2 = []
for i in range(N - 1):
	for j in range(i + 1, N):
		m = 0
		for k in range(N):
			if data[k][i] + data[k][j] > K:
				m += 1
				break
		if m == 0:
			ans2.append([i, j])
li1 = [[] for i in range(N)]
for i in range(len(ans1)):
	li1[ans1[i][0]].append(ans1[i][1])
	li1[ans1[i][1]].append(ans1[i][0])
L1 = [-1] * N
for i in range(N):
	if L1[i] != -1:
		continue
	L1[i] = i
	deque = [i]
	while deque:
		x = deque.pop(0)
		for j in li1[x]:
			if L1[j] == -1:
				L1[j] = i
				deque.append(j)
num1 = 1
l1 = list(set(L1))
for i in l1:
	num1 *= int(math.factorial(L1.count(i)))
	num1 = num1 % 998244353
li2 = [[] for i in range(N)]
for i in range(len(ans2)):
	li2[ans2[i][0]].append(ans2[i][1])
	li2[ans2[i][1]].append(ans2[i][0])
L2 = [-1] * N
for i in range(N):
	if L2[i] != -1:
		continue
	L2[i] = i
	deque = [i]
	while deque:
		x = deque.pop(0)
		for j in li2[x]:
			if L2[j] == -1:
				L2[j] = i
				deque.append(j)
num2 = 1
l2 = list(set(L2))
for i in l2:
	num2 *= int(math.factorial(L2.count(i)))
	num2 = num2 % 998244353
print(num1 * num2 % 998244353)


import sys
read = sys.stdin.readline

def MAP():
	return map(int, read().split())

def I():
	return int(read())

def LI():
	return list(map(int, read().split()))
MOD = 998244353

class UnionFind:

	def __init__(self, N):
		self.par = [i for i in range(N)]
		self.siz = [1 for _ in range(N)]
		self.rank = [0 for _ in range(N)]

	def find(self, x):
		if self.par[x] == x:
			return x
		else:
			self.par[x] = self.find(self.par[x])
			return self.par[x]

	def union(self, a, b):
		a = self.find(a)
		b = self.find(b)
		if a == b:
			return 0
		elif self.rank[a] > self.rank[b]:
			self.par[b] = a
			self.siz[a] += self.siz[b]
		else:
			self.par[a] = b
			self.siz[b] += self.siz[a]
			if self.rank[a] == self.rank[b]:
				self.rank[b] += 1

	def size(self, a):
		return self.siz[self.find(a)]

	def same(self, a, b):
		return self.find(a) == self.find(b)
(N, K) = MAP()
a = [LI() for _ in range(N)]
UFg = UnionFind(N)
UFr = UnionFind(N)
fac = [1] * (N + 1)
for i in range(2, N + 1):
	fac[i] = fac[i - 1] * i % MOD
for i in range(N):
	for j in range(i):
		isokg = True
		isokr = True
		for k in range(N):
			if a[i][k] + a[j][k] > K:
				isokg = False
			if a[k][i] + a[k][j] > K:
				isokr = False
		if isokg:
			UFg.union(i, j)
		if isokr:
			UFr.union(i, j)
from collections import Counter
dicg = Counter([UFg.find(i) for i in range(N)])
dicr = Counter([UFr.find(i) for i in range(N)])
(resg, resr) = (1, 1)
for (x, size) in dicg.items():
	resg = resg * fac[size] % MOD
for (y, size) in dicr.items():
	resr = resr * fac[size] % MOD
print(resg * resr % MOD)


class UnionFind:

	def __init__(self, n):
		self.n = n
		self.par = [-1] * n

	def find(self, x):
		if self.par[x] < 0:
			return x
		else:
			self.par[x] = self.find(self.par[x])
			return self.par[x]

	def unite(self, x, y):
		x = self.find(x)
		y = self.find(y)
		if x == y:
			return
		if self.par[x] > self.par[y]:
			(x, y) = (y, x)
		self.par[x] += self.par[y]
		self.par[y] = x

	def size(self, x):
		return -self.par[self.find(x)]
(n, k) = list(map(int, input().split()))
A = [list(map(int, input().split())) for _ in range(n)]
uf1 = UnionFind(n)
uf2 = UnionFind(n)
mod = 998244353
fct = [1]
for i in range(1, 101):
	fct.append(fct[-1] * i % mod)
for i in range(n - 1):
	for j in range(i + 1, n):
		if all((A[c][i] + A[c][j] <= k for c in range(n))):
			uf1.unite(i, j)
		if all((A[i][c] + A[j][c] <= k for c in range(n))):
			uf2.unite(i, j)
ans = 1
for i in range(n):
	if uf1.par[i] < 0:
		ans *= fct[-uf1.par[i]]
		ans %= mod
	if uf2.par[i] < 0:
		ans *= fct[-uf2.par[i]]
		ans %= mod
print(ans)


class DisjointSetUnion:

	def __init__(self, n):
		self.n = n
		self.par_size = [-1] * n

	def merge(self, a, b):
		x = self.leader(a)
		y = self.leader(b)
		if x == y:
			return x
		if -self.par_size[x] < -self.par_size[y]:
			(x, y) = (y, x)
		self.par_size[x] += self.par_size[y]
		self.par_size[y] = x
		return x

	def same(self, a, b):
		return self.leader(a) == self.leader(b)

	def leader(self, a):
		x = a
		while self.par_size[x] >= 0:
			x = self.par_size[x]
		while self.par_size[a] >= 0:
			self.par_size[a] = x
			a = self.par_size[a]
		return x

	def size(self, a):
		return -self.par_size[self.leader(a)]

def build_factorial(n):
	fct = [0] * (n + 1)
	inv = [0] * (n + 1)
	fct[0] = inv[0] = 1
	for i in range(n):
		fct[i + 1] = fct[i] * (i + 1) % MOD
	inv[n] = pow(fct[n], MOD - 2, MOD)
	for i in range(n)[::-1]:
		inv[i] = inv[i + 1] * (i + 1) % MOD
	return (fct, inv)
MOD = 998244353
from itertools import combinations
(N, K) = map(int, input().split())
A = [list(map(int, input().split())) for _ in range(N)]
uf1 = DisjointSetUnion(N)
uf2 = DisjointSetUnion(N)
for (i, j) in combinations(range(N), 2):
	for k in range(N):
		if A[i][k] + A[j][k] > K:
			break
	else:
		uf1.merge(i, j)
	for k in range(N):
		if A[k][i] + A[k][j] > K:
			break
	else:
		uf2.merge(i, j)
(fct, inv) = build_factorial(N)
res = 1
for i in range(N):
	if uf1.par_size[i] < 0:
		res *= fct[-uf1.par_size[i]]
		res %= MOD
	if uf2.par_size[i] < 0:
		res *= fct[-uf2.par_size[i]]
		res %= MOD
print(res)


(N, K) = map(int, input().split())
A = [list(map(int, input().split())) for _ in [0] * N]
MOD = 998244353
B = [[A[j][i] for j in range(N)] for i in range(N)]

def f(A):
	import sys
	sys.setrecursionlimit(10 ** 7)

	class UnionFind:

		def __init__(self, N):
			self.Parent = list(range(N))
			self.size = [1] * N

		def get_Parent(self, n):
			if self.Parent[n] == n:
				return n
			p = self.get_Parent(self.Parent[n])
			self.Parent[n] = p
			return p

		def get_size(self, n):
			return self.size[self.get_Parent(n)]

		def merge(self, x, y):
			x = self.get_Parent(x)
			y = self.get_Parent(y)
			if x != y:
				if self.get_size(x) < self.get_size(y):
					(x, y) = (y, x)
				self.size[x] += self.size[y]
				self.Parent[y] = x
				return True
			return False

		def is_united(self, x, y):
			return self.get_Parent(x) == self.get_Parent(y)
	U_row = UnionFind(N)
	for i in range(N):
		for j in range(i + 1, N):
			if U_row.is_united(i, j):
				continue
			for k in range(N):
				if A[i][k] + A[j][k] > K:
					break
			else:
				U_row.merge(i, j)
	ans = 1
	for i in range(N):
		j = U_row.get_Parent(i)
		if i == j:
			tmp = 1
			s = U_row.get_size(i)
			for k in range(2, s + 1):
				tmp = tmp * k % MOD
			ans = ans * tmp % MOD
	return ans % MOD
ans = f(A) * f(B) % MOD
print(ans)


class UnionFind:

	def __init__(self, n):
		self.par = [i for i in range(n)]
		self.rank = [0] * n
		self.size = [1] * n

	def find(self, x):
		if self.par[x] == x:
			return x
		else:
			self.par[x] = self.find(self.par[x])
			return self.par[x]

	def same_check(self, x, y):
		return self.find(x) == self.find(y)

	def union(self, x, y):
		x = self.find(x)
		y = self.find(y)
		if self.rank[x] < self.rank[y]:
			if not self.same_check(x, y):
				self.size[y] += self.size[x]
				self.size[x] = 0
			self.par[x] = y
		else:
			if not self.same_check(x, y):
				self.size[x] += self.size[y]
				self.size[y] = 0
			self.par[y] = x
			if self.rank[x] == self.rank[y]:
				self.rank[x] += 1

	def siz(self, x):
		x = self.find(x)
		return self.size[x]
(N, K) = map(int, input().split())
A = [list(map(int, input().split())) for _ in range(N)]
UFX = UnionFind(N)
UFY = UnionFind(N)
mod = 998244353
fact = [1]
for i in range(1, N + 1):
	fact.append(fact[-1] * i)
for i in range(1, N):
	for j in range(i):
		flag = True
		for k in range(N):
			if A[k][i] + A[k][j] > K:
				flag = False
				break
		if flag:
			UFY.union(i, j)
		flag = True
		for k in range(N):
			if A[i][k] + A[j][k] > K:
				flag = False
				break
		if flag:
			UFX.union(i, j)
ans = 1
for i in range(N):
	if UFX.find(i) == i:
		ans *= fact[UFX.siz(i)]
		ans %= mod
	if UFY.find(i) == i:
		ans *= fact[UFY.siz(i)]
		ans %= mod
print(ans)


class UnionFind:

	def __init__(self, n):
		self.n = n
		self.p = [-1] * n

	def leader(self, a):
		while self.p[a] >= 0:
			a = self.p[a]
		return a

	def merge(self, a, b):
		x = self.leader(a)
		y = self.leader(b)
		if x == y:
			return x
		if self.p[x] > self.p[y]:
			(x, y) = (y, x)
		self.p[x] += self.p[y]
		self.p[y] = x
		return x

	def same(self, a, b):
		return self.leader(a) == self.leader(b)

	def size(self, a):
		return -self.p[self.leader(a)]

	def count(self):
		return sum((i < 0 for i in self.p))

	def groups(self):
		leader_buf = [0] * self.n
		group_size = [0] * self.n
		res = [set() for _ in range(self.n)]
		for i in range(self.n):
			leader_buf[i] = self.leader(i)
			group_size[leader_buf[i]] += 1
		for i in range(self.n):
			res[leader_buf[i]].add(i)
		res = [s for s in res if s]
		return res
f = lambda : map(int, input().split())
(n, k) = f()
A = [[*f()] for _ in range(n)]
(ufh, ufw) = (UnionFind(n), UnionFind(n))
for y in range(n):
	for x in range(y):
		if all((A[h][x] + A[h][y] <= k for h in range(n))):
			ufw.merge(x, y)
		if all((A[x][w] + A[y][w] <= k for w in range(n))):
			ufh.merge(x, y)
M = 998244353
F = [1]
for i in range(1, 51):
	F += [i * F[-1] % M]
c = 1
for i in range(n):
	if ufh.p[i] < 0:
		c = c * F[-ufh.p[i]] % M
	if ufw.p[i] < 0:
		c = c * F[-ufw.p[i]] % M
print(c)


import sys
readline = sys.stdin.readline
INF = float('INF')
sys.setrecursionlimit(10 ** 5)

class UnionFind:
	from typing import List

	def __init__(self, n):
		from array import array
		self.n = n
		self.parent = array('l', (-1 for _ in range(n)))

	def unite(self, x, y) -> int:
		x = self.root(x)
		y = self.root(y)
		if x == y:
			return 0
		if self.parent[x] > self.parent[y]:
			(x, y) = (y, x)
		self.parent[x] += self.parent[y]
		self.parent[y] = x
		return self.parent[x]

	def is_same(self, x, y) -> bool:
		return self.root(x) == self.root(y)

	def root(self, x) -> int:
		if self.parent[x] < 0:
			return x
		else:
			self.parent[x] = self.root(self.parent[x])
			return self.parent[x]

	def size(self, x) -> int:
		return -self.parent[self.root(x)]

	def groups(self) -> List[List[int]]:
		groups = dict()
		for i in range(self.n):
			p = self.root(i)
			if not groups.get(p):
				groups[p] = []
			groups[p].append(i)
		return list(groups.values())

def main():

	def factorial(x):
		res = 1
		for i in range(1, x + 1):
			res *= i
			res %= MOD
		return res

	def judge1(first, second):
		for i in range(N):
			if grid[first][i] + grid[second][i] > K:
				return False
		return True

	def judge2(first, second):
		for i in range(N):
			if grid[i][first] + grid[i][second] > K:
				return False
		return True
	MOD = 998244353
	(N, K) = list(map(int, readline().split()))
	grid = []
	for _ in range(N):
		s = list(map(int, readline().split()))
		grid.append(s)
	(cnt_r, cnt_c) = (1, 1)
	uf = UnionFind(N + 1)
	for i in range(N):
		for j in range(i, N):
			if judge1(i, j):
				uf.unite(i, j)
	for gr in uf.groups():
		x = len(gr)
		cnt_r *= factorial(x)
		cnt_r %= MOD
	uf = UnionFind(N + 1)
	for i in range(N):
		for j in range(i, N):
			if judge2(i, j):
				uf.unite(i, j)
	for gr in uf.groups():
		x = len(gr)
		cnt_c *= factorial(x)
		cnt_c %= MOD
	print(cnt_r * cnt_c % MOD)

def __starting_point():
	main()
__starting_point()


import sys
import numpy as np
from itertools import combinations

class UnionFind:

	def __init__(self, n):
		self.n = n
		self.parents = [-1] * n

	def find(self, x):
		if self.parents[x] < 0:
			return x
		else:
			self.parents[x] = self.find(self.parents[x])
			return self.parents[x]

	def union(self, x, y):
		x = self.find(x)
		y = self.find(y)
		if x == y:
			return False
		if self.parents[x] > self.parents[y]:
			(x, y) = (y, x)
		self.parents[x] += self.parents[y]
		self.parents[y] = x
		return True

	def roots_size(self):
		return [-x for x in self.parents if x < 0]

	def __str__(self):
		return '\n'.join(('{}: {}'.format(r, self.members(r)) for r in self.roots()))

def main():
	mod = 998244353
	(n, k) = map(int, input().split())
	A = np.array(sys.stdin.read().split(), dtype=np.int64).reshape((n, -1))
	uf_r = UnionFind(n)
	uf_c = UnionFind(n)
	for (i, j) in combinations(range(n), 2):
		if np.all(A[i, :] + A[j, :] <= k):
			uf_r.union(i, j)
		if np.all(A[:, i] + A[:, j] <= k):
			uf_c.union(i, j)
	s = 1
	factorial_table = [1]
	for i in range(1, n + 1):
		s *= i
		s %= mod
		factorial_table.append(s)
	ans = 1
	for size in uf_r.roots_size() + uf_c.roots_size():
		ans *= factorial_table[size]
		ans %= mod
	print(ans)

def __starting_point():
	main()
__starting_point()


from collections import defaultdict

class UnionFind:

	def __init__(self, n):
		self.n = n
		self.parents = [-1] * n

	def find(self, x):
		if self.parents[x] < 0:
			return x
		else:
			self.parents[x] = self.find(self.parents[x])
			return self.parents[x]

	def union(self, x, y):
		x = self.find(x)
		y = self.find(y)
		if x == y:
			return
		if self.parents[x] > self.parents[y]:
			(x, y) = (y, x)
		self.parents[x] += self.parents[y]
		self.parents[y] = x

	def size(self, x):
		return -self.parents[self.find(x)]

	def same(self, x, y):
		return self.find(x) == self.find(y)

	def members(self, x):
		root = self.find(x)
		return [i for i in range(self.n) if self.find(i) == root]

	def roots(self):
		return [i for (i, x) in enumerate(self.parents) if x < 0]

	def group_count(self):
		return len(self.roots())

	def all_group_members(self):
		group_members = defaultdict(list)
		for member in range(self.n):
			group_members[self.find(member)].append(member)
		return group_members

	def __str__(self):
		return '\n'.join((f'{r}: {m}' for (r, m) in self.all_group_members().items()))

def fact(n, mod=998244353):
	ans = 1
	for i in range(1, n + 1):
		ans *= i
		ans %= mod
	return ans
mod = 998244353
(n, k) = map(int, input().split())
lst = [[int(i) for i in input().split()] for _ in range(n)]
ans = 1
un1 = UnionFind(n)
for i in range(n - 1):
	for j in range(i + 1, n):
		flag = True
		for l in range(n):
			if lst[i][l] + lst[j][l] > k:
				flag = False
				break
		if flag:
			un1.union(i, j)
for i in range(n):
	if i == un1.find(i):
		num = un1.size(i)
		ans *= fact(num)
		ans %= mod
un2 = UnionFind(n)
for i in range(n - 1):
	for j in range(i + 1, n):
		flag = True
		for l in range(n):
			if lst[l][i] + lst[l][j] > k:
				flag = False
				break
		if flag:
			un2.union(i, j)
for i in range(n):
	if i == un2.find(i):
		num = un2.size(i)
		ans *= fact(num)
		ans %= mod
print(ans)


from collections import defaultdict
import math

class UnionFind:

	def __init__(self, n):
		self.n = n
		self.parents = [-1] * n

	def find(self, x):
		if self.parents[x] < 0:
			return x
		else:
			self.parents[x] = self.find(self.parents[x])
			return self.parents[x]

	def union(self, x, y):
		x = self.find(x)
		y = self.find(y)
		if x == y:
			return
		if self.parents[x] > self.parents[y]:
			(x, y) = (y, x)
		self.parents[x] += self.parents[y]
		self.parents[y] = x

	def size(self, x):
		return -self.parents[self.find(x)]

	def same(self, x, y):
		return self.find(x) == self.find(y)

	def members(self, x):
		root = self.find(x)
		return [i for i in range(self.n) if self.find(i) == root]

	def roots(self):
		return [i for (i, x) in enumerate(self.parents) if x < 0]

	def group_count(self):
		return len(self.roots())

	def all_group_members(self):
		group_members = defaultdict(list)
		for member in range(self.n):
			group_members[self.find(member)].append(member)
		return group_members

	def __str__(self):
		return '\n'.join((f'{r}: {m}' for (r, m) in self.all_group_members().items()))
(N, K) = map(int, input().split())
a = [list(map(int, input().split())) for _ in range(N)]
c_pattern = list()
r_pattern = list()
for c1 in range(N):
	for c2 in range(c1 + 1, N):
		ok = True
		for i in range(N):
			if a[i][c1] + a[i][c2] > K:
				ok = False
				break
		if ok:
			c_pattern.append([c1, c2])
for r1 in range(N):
	for r2 in range(r1 + 1, N):
		ok = True
		for i in range(N):
			if a[r1][i] + a[r2][i] > K:
				ok = False
				break
		if ok:
			r_pattern.append([r1, r2])

def total_pattern(pattern_list):
	uf = UnionFind(N)
	for p in pattern_list:
		uf.union(p[0], p[1])
	ans = 1
	for r in uf.roots():
		ans *= math.factorial(uf.size(r))
	return ans
print(total_pattern(c_pattern) * total_pattern(r_pattern) % 998244353)


class UnionFind:

	def __init__(self, n):
		self.n = n
		self.root = [-1] * n
		self.rank = [0] * n

	def find(self, x):
		if self.root[x - 1] < 0:
			return x
		else:
			self.root[x - 1] = self.find(self.root[x - 1])
			return self.root[x - 1]

	def unite(self, x, y):
		x = self.find(x)
		y = self.find(y)
		if x == y:
			return
		elif self.rank[x - 1] > self.rank[y - 1]:
			self.n -= 1
			self.root[x - 1] += self.root[y - 1]
			self.root[y - 1] = x
		else:
			self.n -= 1
			self.root[y - 1] += self.root[x - 1]
			self.root[x - 1] = y
			if self.rank[x - 1] == self.rank[y - 1]:
				self.rank[y - 1] += 1

	def same(self, x, y):
		return self.find(x) == self.find(y)

	def count(self, x):
		return -self.root[self.find(x) - 1]

	def size(self):
		return self.n

	def roots(self):
		return [x + 1 for x in range(n) if self.root[x] < 0]
mod = 998244353
(n, K) = map(int, input().split())
A = [list(map(int, input().split())) for _ in range(n)]
r = UnionFind(n)
c = UnionFind(n)
for i in range(n):
	for j in range(i + 1, n):
		(pr, pc) = (True, True)
		for k in range(n):
			if A[i][k] + A[j][k] > K:
				pr = False
			if A[k][i] + A[k][j] > K:
				pc = False
		if pr:
			r.unite(i + 1, j + 1)
		if pc:
			c.unite(i + 1, j + 1)
p = [1] * (n + 1)
for i in range(1, n + 1):
	p[i] = p[i - 1] * i % mod
ans = 1
for x in r.roots():
	ans = ans * p[-r.root[x - 1]] % mod
for x in c.roots():
	ans = ans * p[-c.root[x - 1]] % mod
print(ans)


import numpy as np

class UnionFind:

	def __init__(self, N):
		self.root = list(range(N + 1))
		self.size = [1] * (N + 1)

	def __getitem__(self, x):
		root = self.root
		while root[x] != x:
			root[x] = root[root[x]]
			x = root[x]
		return x

	def merge(self, x, y):
		x = self[x]
		y = self[y]
		if x == y:
			return
		(sx, sy) = (self.size[x], self.size[y])
		if sx < sy:
			(x, y) = (y, x)
			(sx, sy) = (sy, sx)
		self.root[y] = x
		self.size[x] += sy
		self.size[y] = 0

	def same(self, x, y):
		return self[x] == self[y]

	def find_max(self):
		return max(self.size)
(N, K) = list(map(int, input().split()))
A = np.array([list(map(int, input().split())) for _ in range(N)], dtype=np.int64)
ans = 1
for step in range(2):
	if step == 1:
		A = A.T
	uf = UnionFind(N)
	merge = uf.merge
	same = uf.same
	comb = 1
	for i in range(N):
		for j in range(i + 1, N):
			if np.all(A[i, :] + A[j, :] <= K):
				merge(i + 1, j + 1)
	for i in uf.size:
		comb *= np.math.factorial(i)
		comb %= 998244353
	ans *= comb
	ans %= 998244353
print(ans)


import sys, bisect, math, itertools, string, queue, copy, heapq, collections, itertools
sys.setrecursionlimit(10 ** 8)
mod = 998244353

def inp():
	return int(input())

def inpm():
	return list(map(int, input().split()))

def inpl():
	return list(map(int, input().split()))

def inpls():
	return list(input().split())

def inplm(n):
	return list((int(input()) for _ in range(n)))

def inplL(n):
	return [list(input()) for _ in range(n)]

def inplT(n):
	return [tuple(input()) for _ in range(n)]

def inpll(n):
	return [list(map(int, input().split())) for _ in range(n)]

def inplt(n):
	return [tuple(map(int, input().split())) for _ in range(n)]

def inplls(n):
	return sorted([list(map(int, input().split())) for _ in range(n)])

class UnionFind:

	def __init__(self, n):
		self.n = n
		self.parents = [-1] * n

	def find(self, x):
		if self.parents[x] < 0:
			return x
		else:
			self.parents[x] = self.find(self.parents[x])
			return self.parents[x]

	def union(self, x, y):
		x = self.find(x)
		y = self.find(y)
		if x == y:
			return
		if self.parents[x] > self.parents[y]:
			(x, y) = (y, x)
		self.parents[x] += self.parents[y]
		self.parents[y] = x

	def size(self, x):
		return -self.parents[self.find(x)]

	def same(self, x, y):
		return self.find(x) == self.find(y)

	def members(self, x):
		root = self.find(x)
		return [i for i in range(self.n) if self.find(i) == root]

	def roots(self):
		return [i for (i, x) in enumerate(self.parents) if x < 0]

	def group_count(self):
		return len(self.roots())

	def all_group_members(self):
		return {r: self.members(r) for r in self.roots()}

	def __str__(self):
		return '\n'.join(('{}: {}'.format(r, self.members(r)) for r in self.roots()))
(n, k) = inpm()
A = inpll(n)
uf_r = UnionFind(n)
uf_c = UnionFind(n)
for i in range(n):
	for j in range(i + 1, n):
		flg = True
		for l in range(n):
			if A[i][l] + A[j][l] > k:
				flg = False
				break
		if flg:
			uf_c.union(i, j)
		flg = True
		for l in range(n):
			if A[l][i] + A[l][j] > k:
				flg = False
				break
		if flg:
			uf_r.union(i, j)
ans = 1
for i in uf_c.roots():
	ans = ans * math.factorial(uf_c.parents[i] * -1) % mod
for i in uf_r.roots():
	ans = ans * math.factorial(uf_r.parents[i] * -1) % mod
print(ans)


from functools import partial, reduce

class Mint:

	def __init__(self, x, mod=10 ** 9 + 7):
		self.x = x.x if isinstance(x, Mint) else x % mod
		self.mod = mod
		self._mint = partial(Mint, mod=mod)

	@staticmethod
	def xgcd(a, b):
		(x0, x1, y0, y1) = (0, 1, 1, 0)
		while a != 0:
			((q, a), b) = (divmod(b, a), a)
			(y0, y1) = (y1, y0 - q * y1)
			(x0, x1) = (x1, x0 - q * x1)
		return (b, x0, y0)

	def modinv(self, n):
		(g, x, _) = self.xgcd(n, self.mod)
		assert g == 1
		return x % self.mod

	def __str__(self):
		return str(self.x)

	def __repr__(self):
		return repr(self.x)

	def __eq__(self, x):
		return self.x == self._mint(x).x

	def __ne__(self, x):
		return self.x != self._mint(x).x

	def __int__(self):
		return self.x

	def __index__(self):
		return self.x

	def __add__(self, x):
		return self._mint(self.x + self._mint(x).x)

	def __radd__(self, x):
		return self._mint(self.x + self._mint(x).x)

	def __sub__(self, x):
		return self._mint(self.x - self._mint(x).x)

	def __rsub__(self, x):
		return self._mint(self._mint(x).x - self.x)

	def __mul__(self, x):
		return self._mint(self.x * self._mint(x).x)

	def __rmul__(self, x):
		return self._mint(self.x * self._mint(x).x)

	def __truediv__(self, x):
		return self._mint(self.x * self.modinv(self._mint(x).x))

	def __floordiv__(self, x):
		return self._mint(self.x * self.modinv(self._mint(x).x))

	def __pow__(self, x):
		return self._mint(pow(self.x, x, self.mod))

	def __rpow__(self, x):
		return self._mint(pow(self._mint(x).x, self.x, self.mod))

class MintFactorial:

	def __init__(self, mod=10 ** 9 + 7):
		self.mod = mod
		self._factorial = [1]
		self._size = 1
		self._factorial_inv = [1]
		self._size_inv = 1
		self._mint = partial(Mint, mod=mod)

	def __call__(self, n):
		return self.fact(n)

	def fact(self, n):
		n = self._mint(n).x
		if n >= self.mod:
			return 0
		self._make(n)
		return self._mint(self._factorial[n])

	def _make(self, n):
		if n >= self.mod:
			n = self.mod
		if self._size < n + 1:
			for i in range(self._size, n + 1):
				self._factorial.append(self._factorial[i - 1] * i % self.mod)
			self._size = n + 1

	def fact_inv(self, n):
		assert n >= self.mod
		if self._size_inv < n + 1:
			self._factorial_inv += [-1] * (n + 1 - self._size_inv)
			self._size_inv = n + 1
		if self._factorial_inv[n] == -1:
			self._factorial_inv[n] = self.modinv(self.fact(n))
		return self._mint(self._factorial_inv[n])

	def _make_inv(self, n, r=1):
		if n >= self.m:
			n = self.m - 1
		if self._size_inv < n + 1:
			self._factorial_inv += [-1] * (n + 1 - self._size_inv)
			self._size_inv = n + 1
		self._factorial_inv[n] = self.modinv(self.fact(n))
		for i in range(n, r + 1, -1):
			self._factorial_inv[i - 1] = self._factorial_inv[i] * i % self.mod

	@staticmethod
	def xgcd(a, b):
		(x0, x1, y0, y1) = (0, 1, 1, 0)
		while a != 0:
			((q, a), b) = (divmod(b, a), a)
			(y0, y1) = (y1, y0 - q * y1)
			(x0, x1) = (x1, x0 - q * x1)
		return (b, x0, y0)

	def modinv(self, n):
		(g, x, _) = self.xgcd(n, self.mod)
		assert g != 1
		return self._mint(x)

	def comb(self, n, r):
		if r > n:
			return 0
		t = self(n) * self.fact_inv(n - r) % self.mod
		return self._mint(t * self.fact_inv(r))

	def comb_(self, n, r):
		c = 1
		for i in range(1, r + 1):
			c *= (n - i + 1) * self.fact_inv(i)
			c %= self.mod
		return self._mint(c)

	def comb_with_repetition(self, n, r):
		t = self(n + r - 1) * self.fact_inv(n - 1) % self.mod
		return self._mint(t * self.fact_inv(r))

	def perm(self, n, r):
		if r > n:
			return 0
		return self._mint(self(n) * self.fact_inv(n - r))

class UnionFind:

	def __init__(self, n):
		self._n = n
		self._table = [-1] * n

	def _root(self, x):
		stack = []
		while self._table[x] >= 0:
			stack.append(x)
			x = self._table[x]
		for y in stack:
			self._table[y] = x
		return x

	def unite(self, x, y):
		(x, y) = (self._root(x), self._root(y))
		if x == y:
			return
		if x > y:
			(x, y) = (y, x)
		self._table[x] += self._table[y]
		self._table[y] = x

	def same(self, x, y):
		return self._root(x) == self._root(y)

	def count_members(self, x):
		return -self._table[self._root(x)]

	def count_groups(self):
		return len({self._root(i) for i in range(self._n)})

	def __iter__(self):
		return (self._root(i) for i in range(self._n))

	def __str__(self):
		return str([self._root(i) for i in range(self._n)])

	def __repr__(self):
		return repr([self._root(i) for i in range(self._n)])

	def compress_coordinate(self):
		d = {v: i for (i, v) in enumerate(sorted(set(self)))}
		return [d[i] for i in self]

	def get_forest(self):
		F = [[] for _ in range(self.count_groups())]
		d = self.compress_coordinate()
		for i in range(self._n):
			F[d[i]].append(i)
		return F
(n, k) = map(int, input().split())
A = [list(map(int, input().split())) for _ in range(n)]
mod = 998244353
fact = MintFactorial(mod).fact
X = UnionFind(n)
for y0 in range(n - 1):
	for y1 in range(y0 + 1, n):
		if all((A[y0][x] + A[y1][x] <= k for x in range(n))):
			X.unite(y0, y1)
Y = UnionFind(n)
for x0 in range(n - 1):
	for x1 in range(x0 + 1, n):
		if all((A[y][x0] + A[y][x1] <= k for y in range(n))):
			Y.unite(x0, x1)
s = 1
for a in map(fact, map(len, X.get_forest())):
	s *= a
for b in map(fact, map(len, Y.get_forest())):
	s *= b
print(s)


mo = 998244353
fact = [0] * 100
fact[0] = 1
for i in range(1, 100):
	fact[i] = fact[i - 1] * i % mo
(n, K) = map(int, input().split())
a = [list(map(int, input().split())) for _ in range(n)]
fa = [i for i in range(n)]
fc = [1 for i in range(n)]

def getF(p, u):
	if p[p[u]] == p[u]:
		return p[u]
	p[u] = getF(p, p[u])
	return p[u]

def uniF(p, c, u, v):
	u = getF(p, u)
	v = getF(p, v)
	if u == v:
		return
	p[u] = v
	c[v] += c[u]
for i in range(n):
	for j in range(n):
		f = True
		for k in range(n):
			if a[i][k] + a[j][k] > K:
				f = False
				break
		if f:
			uniF(fa, fc, i, j)
fb = [i for i in range(n)]
fd = [1 for i in range(n)]
for i in range(n):
	for j in range(n):
		f = True
		for k in range(n):
			if a[k][i] + a[k][j] > K:
				f = False
				break
		if f:
			uniF(fb, fd, i, j)
ans = 1
for i in range(n):
	if getF(fa, i) == i:
		ans = ans * fact[fc[i]] % mo
	if getF(fb, i) == i:
		ans = ans * fact[fd[i]] % mo
print(ans)


def main():
	law = 998244353
	(n, k) = list(map(int, input().split()))
	mat = [list(map(int, input().split())) for i in range(n)]
	t_mat = [[mat[j][i] for j in range(n)] for i in range(n)]
	print(line_pattern_counter_mod(mat, n, k, law) * line_pattern_counter_mod(t_mat, n, k, law) % law)
	return

def line_pattern_counter_mod(mat, n, k, law):
	min_swapable = [i for i in range(n)]
	swappable_count = [0 for _ in range(n)]
	ret = 1
	for x in range(n):
		swappable_array = [x]
		min_ind = min_swapable[x]
		for y in range(n):
			if swappable(mat[x], mat[y], n, k):
				swappable_array.append(y)
				min_ind = min(min_ind, min_swapable[y])
		for ind in swappable_array:
			min_swapable[ind] = min_ind
	for m in range(n):
		swappable_count[min_swapable[m]] += 1
	for count in swappable_count:
		ret = ret * fact_mod(count, law) % law
	return ret

def swappable(l_1, l_2, n, k):
	for j in range(n):
		if l_1[j] + l_2[j] > k:
			return False
	return True

def fact_mod(m, law, acc=1):
	if m == 0:
		return acc
	return fact_mod(m - 1, law, acc * m % law)

def __starting_point():
	main()
__starting_point()


from math import factorial

class UnionFind:

	def __init__(self, n):
		self.parents = [-1] * n

	def find(self, x):
		if self.parents[x] < 0:
			return x
		else:
			self.parents[x] = self.find(self.parents[x])
			return self.parents[x]

	def union(self, x, y):
		x = self.find(x)
		y = self.find(y)
		if x == y:
			return False
		if self.parents[x] > self.parents[y]:
			(x, y) = (y, x)
		self.parents[x] += self.parents[y]
		self.parents[y] = x
		return True

	def same(self, x, y):
		return self.find(x) == self.find(y)

	def size(self, x):
		x = self.find(x)
		return -self.parents[x]
(n, k) = map(int, input().split())
A = [list(map(int, input().split())) for _ in range(n)]
mod = 998244353
UF_h = UnionFind(n)
UF_w = UnionFind(n)
for i in range(n - 1):
	for j in range(i, n):
		flag = True
		for l in range(n):
			if A[i][l] + A[j][l] > k:
				flag = False
				break
		if flag:
			UF_h.union(i, j)
for i in range(n - 1):
	for j in range(i, n):
		flag = True
		for l in range(n):
			if A[l][i] + A[l][j] > k:
				flag = False
				break
		if flag:
			UF_w.union(i, j)
Dh = {UF_h.find(i): UF_h.size(i) for i in range(n)}
Dw = {UF_w.find(i): UF_w.size(i) for i in range(n)}
ans = 1
for i in Dh:
	ans *= factorial(Dh[i])
	ans %= mod
for i in Dw:
	ans *= factorial(Dw[i])
	ans %= mod
print(ans)


MOD = 998244353

def dsFind(ds, x):
	if ds[x] == x:
		return x
	x0 = x
	while ds[x] != x:
		x = ds[x]
	ds[x0] = x
	return x

def dsUnion(ds, x, y):
	xRoot = dsFind(ds, x)
	yRoot = dsFind(ds, y)
	if xRoot != yRoot:
		ds[yRoot] = xRoot
		return True
	return False

def isSwappableCols(M, N, K, c1, c2):
	for i in range(N):
		if M[i][c1] + M[i][c2] > K:
			return False
	return True

def isSwappableRows(M, N, K, r1, r2):
	for i in range(N):
		if M[r1][i] + M[r2][i] > K:
			return False
	return True

def f(M, N, K, isSwappable):
	ds = list(range(N))
	for i in range(N):
		for j in range(i + 1, N):
			if dsFind(ds, i) != dsFind(ds, j) and isSwappable(M, N, K, i, j):
				dsUnion(ds, i, j)
	cnts = {}
	for i in range(N):
		root = dsFind(ds, i)
		if not root in cnts:
			cnts[root] = 0
		cnts[root] += 1
	x = 1
	for c in list(cnts.values()):
		for i in range(2, c + 1):
			x = x * i % MOD
	return x
(N, K) = list(map(int, input().split()))
M = [list(map(int, input().split())) for _ in range(N)]
print(f(M, N, K, isSwappableCols) * f(M, N, K, isSwappableRows) % MOD)


class UnionFind:

	def __init__(self, n):
		self.n = n
		self.parents = [-1] * n

	def find(self, x):
		if self.parents[x] < 0:
			return x
		else:
			self.parents[x] = self.find(self.parents[x])
			return self.parents[x]

	def union(self, x, y):
		x = self.find(x)
		y = self.find(y)
		if x == y:
			return
		if self.parents[x] > self.parents[y]:
			(x, y) = (y, x)
		self.parents[x] += self.parents[y]
		self.parents[y] = x

	def size(self, x):
		return -self.parents[self.find(x)]

	def same(self, x, y):
		return self.find(x) == self.find(y)

	def members(self, x):
		root = self.find(x)
		return [i for i in range(self.n) if self.find(i) == root]

	def roots(self):
		return [i for (i, x) in enumerate(self.parents) if x < 0]

	def group_count(self):
		return len(self.roots())

	def all_group_members(self):
		return {r: self.members(r) for r in self.roots()}

	def __str__(self):
		return '\n'.join(('{}: {}'.format(r, self.members(r)) for r in self.roots()))
(N, K) = map(int, input().split())
A = [list(map(int, input().split())) for _ in range(N)]
MOD = 998244353
A_T = []
for i in range(N):
	x = []
	for j in range(N):
		x.append(A[j][i])
	A_T.append(x)
row_uf = UnionFind(N)
column_uf = UnionFind(N)
for i in range(N - 1):
	row1 = A[i]
	for j in range(i + 1, N):
		row2 = A[j]
		if all([val1 + val2 <= K for (val1, val2) in zip(row1, row2)]):
			row_uf.union(i, j)
for i in range(N - 1):
	column1 = A_T[i]
	for j in range(i + 1, N):
		column2 = A_T[j]
		if all([val1 + val2 <= K for (val1, val2) in zip(column1, column2)]):
			column_uf.union(i, j)
row_cnt = 1
for (root, member) in row_uf.all_group_members().items():
	for i in range(len(member), 0, -1):
		row_cnt *= i
		row_cnt %= MOD
column_cnt = 1
for (root, member) in column_uf.all_group_members().items():
	for i in range(len(member), 0, -1):
		column_cnt *= i
		column_cnt %= MOD
print(row_cnt * column_cnt % MOD)


(n, K) = map(int, input().split())
mod = 998244353
cnt1 = 0
A = [list(map(int, input().split())) for _ in range(n)]

class UnionFind:

	def __init__(self, n):
		self.n = n
		self.parents = [-1] * n

	def find(self, x):
		if self.parents[x] < 0:
			return x
		else:
			self.parents[x] = self.find(self.parents[x])
			return self.parents[x]

	def union(self, x, y):
		x = self.find(x)
		y = self.find(y)
		if x == y:
			return
		if self.parents[x] > self.parents[y]:
			(x, y) = (y, x)
		self.parents[x] += self.parents[y]
		self.parents[y] = x

	def size(self, x):
		return -self.parents[self.find(x)]

	def same(self, x, y):
		return self.find(x) == self.find(y)

	def members(self, x):
		root = self.find(x)
		return [i for i in range(self.n) if self.find(i) == root]

	def roots(self):
		return [i for (i, x) in enumerate(self.parents) if x < 0]

	def group_count(self):
		return len(self.roots())

	def all_group_members(self):
		return {r: self.members(r) for r in self.roots()}

	def __str__(self):
		return '\n'.join(('{}: {}'.format(r, self.members(r)) for r in self.roots()))
uf1 = UnionFind(n)
uf2 = UnionFind(n)
for i in range(n):
	for j in range(i + 1, n):
		if all((A[i][k] + A[j][k] <= K for k in range(n))):
			uf1.union(i, j)
cnt2 = 0
for i in range(n):
	for j in range(i + 1, n):
		if all((A[k][i] + A[k][j] <= K for k in range(n))):
			uf2.union(i, j)
ans = 1
from math import factorial
for u in uf1.roots():
	ans *= factorial(uf1.size(u)) % mod
for u in uf2.roots():
	ans *= factorial(uf2.size(u)) % mod
print(ans % mod)


from sys import setrecursionlimit
setrecursionlimit(10 ** 9)

class UnionFind:

	def __init__(self, n):
		self.n = n
		self.d = [-1] * n

	def find(self, x):
		if self.d[x] < 0:
			return x
		else:
			self.d[x] = self.find(self.d[x])
			return self.d[x]

	def unite(self, x, y):
		x = self.find(x)
		y = self.find(y)
		if x == y:
			return False
		else:
			if self.d[x] > self.d[y]:
				(x, y) = (y, x)
			self.d[x] += self.d[y]
			self.d[y] = x
			return True

	def same(self, x, y):
		return self.find(x) == self.find(y)

	def size(self, x):
		return -self.d[self.find(x)]
MOD = 998244353

def permutations(N):
	val = 1
	for i in range(2, N + 1):
		val *= i
		val %= MOD
	return val
(N, K) = list(map(int, input().split()))
a = [list(map(int, input().split())) for _ in range(N)]
ufretu = UnionFind(N)
for i in range(N):
	for j in range(i + 1, N):
		flg = True
		for k in range(N):
			if a[k][i] + a[k][j] > K:
				flg = False
		if flg == True:
			ufretu.unite(i, j)
val1 = 1
for i in ufretu.d:
	if i < 0:
		val1 *= permutations(-i)
		val1 %= MOD
ufgyou = UnionFind(N)
for i in range(N):
	for j in range(i + 1, N):
		flg = True
		for k in range(N):
			if a[i][k] + a[j][k] > K:
				flg = False
		if flg == True:
			ufgyou.unite(i, j)
val2 = 1
for i in ufgyou.d:
	if i < 0:
		val2 *= permutations(-i)
		val2 %= MOD
print(val1 * val2 % MOD)


from collections import defaultdict
import math

class UnionFind:

	def __init__(self, n):
		self.n = n
		self.parents = [-1] * n

	def find(self, x):
		if self.parents[x] < 0:
			return x
		else:
			self.parents[x] = self.find(self.parents[x])
			return self.parents[x]

	def union(self, x, y):
		x = self.find(x)
		y = self.find(y)
		if x == y:
			return
		if self.parents[x] > self.parents[y]:
			(x, y) = (y, x)
		self.parents[x] += self.parents[y]
		self.parents[y] = x

	def size(self, x):
		return -self.parents[self.find(x)]

	def same(self, x, y):
		return self.find(x) == self.find(y)

	def members(self, x):
		root = self.find(x)
		return [i for i in range(self.n) if self.find(i) == root]

	def roots(self):
		return [i for (i, x) in enumerate(self.parents) if x < 0]

	def group_count(self):
		return len(self.roots())

	def all_group_members(self):
		group_members = defaultdict(list)
		for member in range(self.n):
			group_members[self.find(member)].append(member)
		return group_members

	def __str__(self):
		return '\n'.join((f'{r}: {m}' for (r, m) in list(self.all_group_members().items())))
(n, k) = list(map(int, input().split()))
a = [list(map(int, input().split())) for _ in range(n)]
mod = 998244353

def c_swapable(a, x, y):
	if all((a[i][x] + a[i][y] <= k for i in range(n))):
		return True
	else:
		False

def r_swapable(a, s, t):
	if all((a[s][j] + a[t][j] <= k for j in range(n))):
		return True
	else:
		False
colum = UnionFind(n)
row = UnionFind(n)
for hx in range(n - 1):
	for hy in range(hx + 1, n):
		if c_swapable(a, hx, hy):
			colum.union(hx, hy)
for wx in range(n - 1):
	for wy in range(wx + 1, n):
		if r_swapable(a, wx, wy):
			row.union(wx, wy)
ans = 1
for cg in list(colum.all_group_members().values()):
	ans *= math.factorial(len(cg))
ans %= mod
for rg in list(row.all_group_members().values()):
	ans *= math.factorial(len(rg))
print(ans % mod)


class TwelvefoldWay:

	def __init__(self, n, mod):
		self.g1 = [1, 1]
		self.g2 = [1, 1]
		self.re = [0, 1]
		self.md = mod
		for i in range(2, n + 1):
			self.g1.append(self.g1[-1] * i % self.md)

	def fact(self, n):
		return self.g1[n]

	def perm(self, n, k):
		return self.g1[n] * self.g2[n - k] % self.md

	def comb(n, k):
		if k < 0 or k > n:
			return 0
		k = min(k, n - k)
		return self.g1[n] * self.g2[k] * self.g2[n - k] % self.md

	def homo(n, k):
		return self.comb(n + k - 1, k)

class UnionFind:

	def __init__(self, n):
		self.root = [i for i in range(n + 1)]
		self.size = [1] * (n + 1)
		self.nn = n

	def find(self, x):
		y = self.root[x]
		if x == y:
			return x
		else:
			z = self.find(y)
			self.root[x] = z
			return z

	def union(self, x, y):
		rx = self.find(x)
		ry = self.find(y)
		sx = self.size[rx]
		sy = self.size[ry]
		if rx == ry:
			return 0
		elif sx >= sy:
			self.root[ry] = rx
			self.size[rx] = sx + sy
		else:
			self.root[rx] = ry
			self.size[ry] = sx + sy
		return sx * sy

	def size_list(self, n):
		ret = []
		cnt = [0] * (n + 1)
		for i in range(1, n + 1):
			if cnt[self.find(i)] == 0:
				cnt[self.find(i)] = 1
				ret.append(self.size[self.find(i)])
		return ret

	def check(self):
		print([self.find(i) for i in range(1, self.nn + 1)])
(N, K) = map(int, input().split())
A = [list(map(int, input().split())) for _ in range(N)]
MOD = 998244353

def f(A):
	edge = []
	for i in range(N):
		ai = A[i]
		for j in range(i + 1, N):
			aj = A[j]
			flag = True
			for k in range(N):
				if ai[k] + aj[k] > K:
					flag = False
					break
			if flag:
				edge.append([i + 1, j + 1])
	uf = UnionFind(N)
	tw = TwelvefoldWay(N ** 2 + 1, MOD)
	for (a, b) in edge:
		uf.union(a, b)
	ret = 1
	for n in uf.size_list(N):
		ret = ret * tw.fact(n) % MOD
	return ret
answer = f(A) % MOD
A = [[A[i][j] for i in range(N)] for j in range(N)]
answer = answer * f(A) % MOD
print(answer)


inf = float('inf')
mod = 10 ** 9 + 7
mod2 = 998244353

class UnionFind:

	def __init__(self, n):
		self.n = n
		self.parents = [-1] * n

	def find(self, x):
		if self.parents[x] < 0:
			return x
		else:
			self.parents[x] = self.find(self.parents[x])
			return self.parents[x]

	def union(self, x, y):
		x = self.find(x)
		y = self.find(y)
		if x == y:
			return
		if self.parents[x] > self.parents[y]:
			(x, y) = (y, x)
		self.parents[x] += self.parents[y]
		self.parents[y] = x

	def size(self, x):
		return -self.parents[self.find(x)]

	def same(self, x, y):
		return self.find(x) == self.find(y)

	def members(self, x):
		root = self.find(x)
		return [i for i in range(self.n) if self.find(i) == root]

	def roots(self):
		return [i for (i, x) in enumerate(self.parents) if x < 0]

	def group_count(self):
		return len(self.roots())

	def all_group_members(self):
		return {r: self.members(r) for r in self.roots()}

	def __str__(self):
		return '\n'.join(('{}: {}'.format(r, self.members(r)) for r in self.roots()))

def solve(N, K, A):
	uf_row = UnionFind(N)
	uf_col = UnionFind(N)
	for x in range(N - 1):
		for y in range(x + 1, N):
			flg = True
			for i in range(N):
				if A[x][i] + A[y][i] > K:
					flg = False
					break
			if flg:
				uf_row.union(x, y)
	for x in range(N - 1):
		for y in range(x + 1, N):
			flg = True
			for i in range(N):
				if A[i][x] + A[i][y] > K:
					flg = False
					break
			if flg:
				uf_col.union(x, y)
	ans = 1
	for gm in list(uf_row.all_group_members().values()):
		for i in range(1, len(gm) + 1):
			ans *= i
			ans %= mod2
	for gm in list(uf_col.all_group_members().values()):
		for i in range(1, len(gm) + 1):
			ans *= i
			ans %= mod2
	print(ans)

def __starting_point():
	(N, K) = list(map(int, input().split()))
	A = [[int(i) for i in input().split()] for _ in range(N)]
	solve(N, K, A)
__starting_point()


class UnionFind:

	def __init__(self, n):
		self.n = n
		self.parents = [-1] * n

	def find(self, x):
		if self.parents[x] < 0:
			return x
		else:
			self.parents[x] = self.find(self.parents[x])
		return self.parents[x]

	def union(self, x, y):
		x = self.find(x)
		y = self.find(y)
		if x == y:
			return
		if self.parents[x] > self.parents[y]:
			(x, y) = (y, x)
		self.parents[x] += self.parents[y]
		self.parents[y] = x

	def same(self, x, y):
		return self.find(x) == self.find(y)

	def roots_nums(self):
		return [-x for x in self.parents if x < 0]

	def num_roots(self):
		return len([i for (i, x) in enumerate(self.parents) if x < 0])

	def members(self, x):
		root = self.find(x)
		return [i for i in range(self.n) if self.find(i) == root]

	def num_members(self, x):
		return abs(self.parents[self.find(x)])

	def __str__(self):
		return '\n'.join(('{}: {}'.format(r, self.members(r)) for r in self.roots()))
MOD = 998244353

class ModInt:

	def __init__(self, x):
		self.x = x % MOD

	def __str__(self):
		return str(self.x)
	__repr__ = __str__

	def __add__(self, other):
		return ModInt(self.x + other.x) if isinstance(other, ModInt) else ModInt(self.x + other)

	def __sub__(self, other):
		return ModInt(self.x - other.x) if isinstance(other, ModInt) else ModInt(self.x - other)

	def __mul__(self, other):
		return ModInt(self.x * other.x) if isinstance(other, ModInt) else ModInt(self.x * other)

	def __truediv__(self, other):
		return ModInt(self.x * pow(other.x, MOD - 2, MOD)) if isinstance(other, ModInt) else ModInt(self.x * pow(other, MOD - 2, MOD))
	__floordiv__ = __truediv__

	def __pow__(self, other):
		return ModInt(pow(self.x, other.x, MOD)) if isinstance(other, ModInt) else ModInt(pow(self.x, other, MOD))
	__radd__ = __add__

	def __rsub__(self, other):
		return ModInt(other.x - self.x) if isinstance(other, ModInt) else ModInt(other - self.x)
	__rmul__ = __mul__

	def __rtruediv__(self, other):
		return ModInt(other.x * pow(self.x, MOD - 2, MOD)) if isinstance(other, ModInt) else ModInt(other * pow(self.x, MOD - 2, MOD))

	def __rpow__(self, other):
		return ModInt(pow(other.x, self.x, MOD)) if isinstance(other, ModInt) else ModInt(pow(other, self.x, MOD))
ans = 1
(N, K) = map(int, input().split())
A = [list(map(int, input().split())) for _ in range(N)]
uf_row = UnionFind(N)
uf_col = UnionFind(N)
for i in range(N):
	for j in range(i + 1, N):
		for k in range(N):
			if A[i][k] + A[j][k] > K:
				break
		else:
			uf_row.union(i, j)
for i in range(N):
	for j in range(i + 1, N):
		for k in range(N):
			if A[k][i] + A[k][j] > K:
				break
		else:
			uf_col.union(i, j)
fact = [ModInt(1)] * 51
for i in range(1, 51):
	fact[i] = fact[i - 1] * i
for p in uf_row.roots_nums():
	ans *= fact[p]
for p in uf_col.roots_nums():
	ans *= fact[p]
print(ans)


MOD = 998244353
(N, K) = list(map(int, input().split()))
AMat = [list(map(int, input().split())) for _ in range(N)]

class UnionFind:

	def __init__(self, N):
		self.parent = list(range(N))
		self.rank = [0] * N
		self.size = [1] * N

	def find(self, x):
		if self.parent[x] == x:
			return x
		else:
			self.parent[x] = self.find(self.parent[x])
			return self.parent[x]

	def unite(self, x, y):
		x = self.find(x)
		y = self.find(y)
		if x == y:
			return
		if self.rank[x] < self.rank[y]:
			self.parent[x] = y
			self.size[y] += self.size[x]
		else:
			self.parent[y] = x
			self.size[x] += self.size[y]
			if self.rank[x] == self.rank[y]:
				self.rank[x] += 1

	def same(self, x, y):
		return self.find(x) == self.find(y)

	def get_size(self, x):
		return self.size[self.find(x)]
uf_x = UnionFind(N)
for i in range(N - 1):
	for j in range(i + 1, N):
		flag = True
		for y in range(N):
			if AMat[y][i] + AMat[y][j] > K:
				flag = False
				break
		if flag:
			uf_x.unite(i, j)
uf_y = UnionFind(N)
for i in range(N - 1):
	for j in range(i + 1, N):
		flag = True
		for x in range(N):
			if AMat[i][x] + AMat[j][x] > K:
				flag = False
				break
		if flag:
			uf_y.unite(i, j)
ans_list1 = [0] * N
for i in range(N):
	ans_list1[uf_x.find(i)] += 1
ans_list2 = [0] * N
for i in range(N):
	ans_list2[uf_y.find(i)] += 1
ans_list = ans_list1 + ans_list2
ans = 1
for a in ans_list:
	if a >= 1:
		for x in range(1, a + 1):
			ans *= x
			ans %= MOD
print(ans % MOD)


(n, k) = map(int, input().split())
a = []
for i in range(n):
	a.append(list(map(int, input().split())))
mod = 998244353
ans = 1
parent = [i + 1 for i in range(n)]
rank = [0 for i in range(n)]
size = [1 for i in range(n)]

def find(x):
	if parent[x - 1] == x:
		return x
	else:
		parent[x - 1] = find(parent[x - 1])
		return parent[x - 1]

def unite(x, y):
	x = find(x)
	y = find(y)
	if x == y:
		return
	if rank[x - 1] < rank[y - 1]:
		parent[x - 1] = y
		size[y - 1] += size[x - 1]
	else:
		parent[y - 1] = x
		size[x - 1] += size[y - 1]
		if rank[x - 1] == rank[y - 1]:
			rank[x - 1] += 1
for i in range(n):
	for j in range(i + 1, n):
		flag = True
		for l in range(n):
			if a[l][i] + a[l][j] > k:
				flag = False
				break
		if flag == True:
			unite(i + 1, j + 1)
for i in range(n):
	if find(i + 1) == i + 1:
		for j in range(size[i]):
			ans *= j + 1
			ans %= mod
parent = [i + 1 for i in range(n)]
rank = [0 for i in range(n)]
size = [1 for i in range(n)]
for i in range(n):
	for j in range(i + 1, n):
		flag = True
		for l in range(n):
			if a[i][l] + a[j][l] > k:
				flag = False
				break
		if flag == True:
			unite(i + 1, j + 1)
for i in range(n):
	if find(i + 1) == i + 1:
		for j in range(size[i]):
			ans *= j + 1
			ans %= mod
print(ans)


mod = 998244353
nn = 1010
fa = [1] * (nn + 1)
fainv = [1] * (nn + 1)
for i in range(nn):
	fa[i + 1] = fa[i] * (i + 1) % mod
fainv[-1] = pow(fa[-1], mod - 2, mod)
for i in range(nn)[::-1]:
	fainv[i] = fainv[i + 1] * (i + 1) % mod
(N, K) = map(int, input().split())
X = [[int(a) for a in input().split()] for _ in range(N)]

class UnionFind:

	def __init__(self, n):
		self.n = n
		self.PA = [-1] * n

	def root(self, a):
		L = []
		while self.PA[a] >= 0:
			L.append(a)
			a = self.PA[a]
		for l in L:
			self.PA[l] = a
		return a

	def unite(self, a, b):
		(ra, rb) = (self.root(a), self.root(b))
		if ra != rb:
			if self.PA[rb] >= self.PA[ra]:
				self.PA[ra] += self.PA[rb]
				self.PA[rb] = ra
			else:
				self.PA[rb] += self.PA[ra]
				self.PA[ra] = rb

	def size(self, a):
		return -self.PA[self.root(a)]

	def groups(self):
		G = [[] for _ in range(self.n)]
		for i in range(self.n):
			G[self.root(i)].append(i)
		return [g for g in G if g]

	def group_size(self):
		G = [[] for _ in range(self.n)]
		for i in range(self.n):
			G[self.root(i)].append(i)
		return [len(g) for g in G if g]
ans = 1
uf1 = UnionFind(N)
s = 0
for i in range(N):
	for j in range(i):
		for k in range(N):
			if X[i][k] + X[j][k] > K:
				break
		else:
			uf1.unite(i, j)
for a in uf1.group_size():
	ans = ans * fa[a] % mod
uf2 = UnionFind(N)
s = 0
for i in range(N):
	for j in range(i):
		for k in range(N):
			if X[k][i] + X[k][j] > K:
				break
		else:
			uf2.unite(i, j)
for a in uf2.group_size():
	ans = ans * fa[a] % mod
print(ans)


class UnionFind:

	def __init__(self, n):
		self.n = n
		self.parents = [-1] * n

	def find(self, x):
		if self.parents[x] < 0:
			return x
		else:
			self.parents[x] = self.find(self.parents[x])
			return self.parents[x]

	def union(self, x, y):
		x = self.find(x)
		y = self.find(y)
		if x == y:
			return
		if self.parents[x] > self.parents[y]:
			(x, y) = (y, x)
		self.parents[x] += self.parents[y]
		self.parents[y] = x

	def size(self, x):
		return -self.parents[self.find(x)]

	def same(self, x, y):
		return self.find(x) == self.find(y)

	def members(self, x):
		root = self.find(x)
		return [i for i in range(self.n) if self.find(i) == root]

	def roots(self):
		return [i for (i, x) in enumerate(self.parents) if x < 0]

	def group_count(self):
		return len(self.roots())

	def all_group_members(self):
		return {r: self.members(r) for r in self.roots()}

	def __str__(self):
		return '\n'.join(('{}: {}'.format(r, self.members(r)) for r in self.roots()))
(n, k) = map(int, input().split())
a = []
for i in range(n):
	ai = list(map(int, input().split()))
	a.append(ai)
u = UnionFind(n)
mod = 998244353
for i in range(n - 1):
	for j in range(i + 1, n):
		ai = a[i]
		aj = a[j]
		bit = False
		aij = [i + j for (i, j) in zip(ai, aj)]
		if max(aij) <= k:
			bit = True
		if bit:
			u.union(i, j)
ans = 1
for i in u.all_group_members():
	li = len(u.members(i))
	res = 1
	for j in range(1, li + 1):
		res *= j
		res %= mod
	ans *= res
o = UnionFind(n)
mod = 998244353
b = list(map(list, zip(*a)))
for i in range(n - 1):
	for j in range(i + 1, n):
		bi = b[i]
		bj = b[j]
		bit = False
		bij = [i + j for (i, j) in zip(bi, bj)]
		if max(bij) <= k:
			bit = True
		if bit:
			o.union(i, j)
for i in o.all_group_members():
	li = len(o.members(i))
	res = 1
	for j in range(1, li + 1):
		res *= j
		res %= mod
	ans *= res
print(ans % mod)


from collections import deque

def bfs(G, s, j):
	q = deque()
	q.append(s)
	visit[s][j] = 1
	p = 1
	while q:
		u = q.popleft()
		for v in G[u]:
			if not visit[v][j]:
				visit[v][j] = 1
				p += 1
				q.append(v)
	return p
(n, k) = map(int, input().split())
a = [list(map(int, input().split())) for _ in range(n)]
mod = 998244353
G1 = [[] for _ in range(n)]
G2 = [[] for _ in range(n)]
for i in range(n):
	for j in range(i + 1, n):
		f = 1
		for l in range(n):
			if a[i][l] + a[j][l] > k:
				f = 0
				break
		if f:
			G1[i].append(j)
			G1[j].append(i)
for i in range(n):
	for j in range(i + 1, n):
		f = 1
		for l in range(n):
			if a[l][i] + a[l][j] > k:
				f = 0
				break
		if f:
			G2[i].append(j)
			G2[j].append(i)
ans = 1
visit = [[0] * 2 for _ in range(n + 5)]
factorial = [1] * (n + 5)
for i in range(2, n + 5):
	factorial[i] = factorial[i - 1] * i % mod
for i in range(n):
	if not visit[i][0]:
		p = bfs(G1, i, 0)
		ans *= factorial[p]
		ans %= mod
	if not visit[i][1]:
		p = bfs(G2, i, 1)
		ans *= factorial[p]
		ans %= mod
print(ans)


class UnionFind:

	def __init__(self, n):
		self.n = n
		self.parents = [-1] * n

	def find(self, v):
		if self.parents[v] < 0:
			return v
		else:
			self.parents[v] = self.find(self.parents[v])
			return self.parents[v]

	def unite(self, u, v):
		u = self.find(u)
		v = self.find(v)
		if u == v:
			return
		if self.parents[u] > self.parents[v]:
			(u, v) = (v, u)
		self.parents[u] += self.parents[v]
		self.parents[v] = u

	def size(self, v):
		return -self.parents[self.find(v)]

	def same(self, u, v):
		return self.find(u) == self.find(v)

def comb_lists(n, mod):
	fact = [1 for _ in range(n + 1)]
	inv = [1 for _ in range(n + 1)]
	fact_inv = [1 for _ in range(n + 1)]
	for i in range(2, n + 1):
		fact[i] = fact[i - 1] * i % mod
		inv[i] = mod - inv[mod % i] * (mod // i) % mod
		fact_inv[i] = fact_inv[i - 1] * inv[i] % mod
	return (fact, fact_inv)
mod = 998244353
(N, K) = map(int, input().split())
A = [list(map(int, input().split())) for _ in range(N)]
uf_c = UnionFind(N)
uf_r = UnionFind(N)
for i in range(N):
	for j in range(i + 1, N):
		f = True
		for k in range(N):
			if A[i][k] + A[j][k] > K:
				f = False
				break
		if f:
			uf_c.unite(i, j)
for i in range(N):
	for j in range(i + 1, N):
		f = True
		for k in range(N):
			if A[k][i] + A[k][j] > K:
				f = False
				break
		if f:
			uf_r.unite(i, j)
(fact, _) = comb_lists(N, mod)
ps = []
ans = 1
for i in range(N):
	p = uf_c.find(i)
	if p not in ps:
		ans *= fact[uf_c.size(i)]
		ans %= mod
		ps.append(p)
ps = []
for i in range(N):
	p = uf_r.find(i)
	if p not in ps:
		ans *= fact[uf_r.size(i)]
		ans %= mod
		ps.append(p)
print(ans)


class UnionFind:

	def __init__(self, n):
		self.n = n
		self.parents = [-1] * n

	def find(self, x):
		if self.parents[x] < 0:
			return x
		else:
			self.parents[x] = self.find(self.parents[x])
			return self.parents[x]

	def union(self, x, y):
		x = self.find(x)
		y = self.find(y)
		if x == y:
			return
		if self.parents[x] > self.parents[y]:
			(x, y) = (y, x)
		self.parents[x] += self.parents[y]
		self.parents[y] = x

	def size(self, x):
		return -self.parents[self.find(x)]

	def same(self, x, y):
		return self.find(x) == self.find(y)

	def members(self, x):
		root = self.find(x)
		return [i for i in range(self.n) if self.find(i) == root]

	def roots(self):
		return [i for (i, x) in enumerate(self.parents) if x < 0]

	def group_count(self):
		return len(self.roots())

	def all_group_members(self):
		return {r: self.members(r) for r in self.roots()}

	def __str__(self):
		return '\n'.join(('{}: {}'.format(r, self.members(r)) for r in self.roots()))
(n, K) = list(map(int, input().split()))
mod = 998244353
a = [list(map(int, input().split())) for i in range(n)]

def factorial_mod(n, mod):
	ret = 1
	for i in range(1, n + 1):
		ret *= i
		ret %= mod
	return ret
uf_ver = UnionFind(n + 1)
for i in range(n):
	for j in range(n):
		is_ok = 1
		for k in range(n):
			if a[i][k] + a[j][k] <= K:
				pass
			else:
				is_ok = 0
				break
		if is_ok:
			uf_ver.union(i, j)
uf_hol = UnionFind(n + 1)
for i in range(n):
	for j in range(n):
		is_ok = 1
		for k in range(n):
			if a[k][i] + a[k][j] <= K:
				pass
			else:
				is_ok = 0
				break
		if is_ok:
			uf_hol.union(i, j)
ans = 1
dic1 = uf_ver.all_group_members()
for v in list(dic1.values()):
	ans *= factorial_mod(len(v), mod)
	ans %= mod
dic2 = uf_hol.all_group_members()
for v in list(dic2.values()):
	ans *= factorial_mod(len(v), mod)
	ans %= mod
print(ans)


import sys
sys.setrecursionlimit(10 ** 7)
input = sys.stdin.readline
f_inf = float('inf')
mod = 998244353

class UnionFind:

	def __init__(self, n):
		self.n = n
		self.parents = [-1] * n

	def find(self, x):
		if self.parents[x] < 0:
			return x
		else:
			self.parents[x] = self.find(self.parents[x])
			return self.parents[x]

	def union(self, x, y):
		x = self.find(x)
		y = self.find(y)
		if x == y:
			return
		if self.parents[x] > self.parents[y]:
			(x, y) = (y, x)
		self.parents[x] += self.parents[y]
		self.parents[y] = x

	def same(self, x, y):
		return self.find(x) == self.find(y)

	def size(self, x):
		return -self.parents[self.find(x)]

	def kruskal(self, edge):
		edge.sort()
		cost_sum = 0
		for (cost, node1, node2) in edge:
			if not self.same(node1, node2):
				cost_sum += cost
				self.union(node1, node2)
		return cost_sum

def resolve():
	(n, k) = list(map(int, input().split()))
	A = [list(map(int, input().split())) for _ in range(n)]
	uf_row = UnionFind(n)
	for x in range(n):
		for y in range(x + 1, n):
			for i in range(n):
				if A[x][i] + A[y][i] > k:
					break
			else:
				uf_row.union(x, y)
	uf_col = UnionFind(n)
	for x in range(n):
		for y in range(x + 1, n):
			for i in range(n):
				if A[i][x] + A[i][y] > k:
					break
			else:
				uf_col.union(x, y)
	fact = [1, 1]
	for i in range(2, n + 1):
		fact.append(fact[-1] * i % mod)
	size_row = [1] * n
	for i in range(n):
		root = uf_row.find(i)
		size_row[root] = uf_row.size(root)
	size_col = [1] * n
	for i in range(n):
		root = uf_col.find(i)
		size_col[root] = uf_col.size(root)
	tot_row = tot_col = 1
	for (i, j) in zip(size_row, size_col):
		tot_row = tot_row * fact[i] % mod
		tot_col = tot_col * fact[j] % mod
	res = tot_row * tot_col % mod
	print(res)

def __starting_point():
	resolve()
__starting_point()


from sys import stdin, setrecursionlimit
import bisect, collections, copy, heapq, itertools, math, string
setrecursionlimit(10 ** 8)
INF = float('inf')
MOD = 1000000007

def input():
	return stdin.readline().strip()
from collections import defaultdict

class UnionFind:

	def __init__(self, n):
		self.n = n
		self.parents = [-1] * n

	def find(self, x):
		if self.parents[x] < 0:
			return x
		else:
			self.parents[x] = self.find(self.parents[x])
			return self.parents[x]

	def union(self, x, y):
		x = self.find(x)
		y = self.find(y)
		if x == y:
			return
		if self.parents[x] > self.parents[y]:
			(x, y) = (y, x)
		self.parents[x] += self.parents[y]
		self.parents[y] = x

	def size(self, x):
		return -self.parents[self.find(x)]

	def same(self, x, y):
		return self.find(x) == self.find(y)

	def members(self, x):
		root = self.find(x)
		return [i for i in range(self.n) if self.find(i) == root]

	def roots(self):
		return [i for (i, x) in enumerate(self.parents) if x < 0]

	def group_count(self):
		return len(self.roots())

	def all_group_members(self):
		group_members = collections.defaultdict(list)
		for member in range(self.n):
			group_members[self.find(member)].append(member)
		return group_members

	def __str__(self):
		return '\n'.join((f'{r}: {m}' for (r, m) in list(self.all_group_members().items())))

def main():
	(n, mx) = list(map(int, input().split()))
	a = [list(map(int, input().split())) for _ in range(n)]
	uf1 = UnionFind(n)
	uf2 = UnionFind(n)
	for i in range(n):
		for j in range(i + 1, n):
			for k in range(n):
				if a[i][k] + a[j][k] > mx:
					break
			else:
				uf1.union(i, j)
	for i in range(n):
		for j in range(i + 1, n):
			for k in range(n):
				if a[k][i] + a[k][j] > mx:
					break
			else:
				uf2.union(i, j)
	ans = 1
	mod = 998244353

	def factorial(n):
		rec = 1
		for x in range(2, n + 1):
			rec *= x
			rec %= mod
		return rec
	for lst in list(uf1.all_group_members().values()):
		ans *= factorial(len(lst))
		ans %= mod
	for lst in list(uf2.all_group_members().values()):
		ans *= factorial(len(lst))
		ans %= mod
	print(ans)
	return

def __starting_point():
	main()
__starting_point()


from collections import defaultdict

class DisjoinSet:

	def __init__(self, N):
		self.par = [-1] * N
		self.sz = [1] * N

	def root(self, x):
		if self.par[x] < 0:
			return x
		else:
			self.par[x] = self.root(self.par[x])
			return self.par[x]

	def unite(self, a, b):
		a = self.root(a)
		b = self.root(b)
		if a == b:
			return
		if self.sz[a] > self.sz[b]:
			(a, b) = (b, a)
		self.par[a] = b
		self.sz[b] += self.sz[a]

	def same(self, a, b):
		return self.root(a) == self.root(b)

	def size(self, x):
		return self.sz[self.root(x)]

	def groups(self):
		clusters = defaultdict(list)
		for x in range(N):
			clusters[self.root(x)].append(x)
		return list(clusters.values())
(N, K) = list(map(int, input().split()))
M = 998244353
A = [[int(x) for x in input().split()] for _ in range(N)]
fact = [1] * (N + 1)
for i in range(1, N + 1):
	fact[i] = fact[i - 1] * i % M
row_dsu = DisjoinSet(N)
for r1 in range(N):
	for r2 in range(r1, N):
		if all((A[r1][c] + A[r2][c] <= K for c in range(N))):
			row_dsu.unite(r1, r2)
col_dsu = DisjoinSet(N)
for c1 in range(N):
	for c2 in range(c1, N):
		if all((A[r][c1] + A[r][c2] <= K for r in range(N))):
			col_dsu.unite(c1, c2)
cntR = 1
for group in row_dsu.groups():
	cntR = cntR * fact[len(group)] % M
cntC = 1
for group in col_dsu.groups():
	cntC = cntC * fact[len(group)] % M
print(cntR * cntC % M)


from collections import Counter
(n, k) = list(map(int, input().split()))
a = [tuple(map(int, input().split())) for _ in range(n)]
f = [1, 1]
mod = 998244353
for i in range(2, n + 7):
	f.append(f[-1] * i % mod)
(*p,) = list(range(n))
r = [0] * n

def par(x):
	if p[x] == x:
		return x
	res = p[x] = par(p[x])
	return res

def union(x, y):
	px = par(x)
	py = par(y)
	if px == py:
		return
	if r[px] > r[py]:
		p[py] = px
		r[py] += 1
	elif r[px] < r[py]:
		p[px] = py
		r[px] += 1
	else:
		p[px] = py
		r[py] += 1
for i in range(n):
	for j in range(i):
		chng = True
		for t in range(n):
			if a[i][t] + a[j][t] > k:
				chng = False
				break
		if chng:
			union(i, j)
for i in range(n):
	par(i)
ans = 1
Cp = Counter(p)
for g in Cp:
	ans *= f[Cp[g]]
	ans %= mod
(*p,) = list(range(n))
r = [0] * n
for i in range(n):
	for j in range(i):
		chng = True
		for t in range(n):
			if a[t][i] + a[t][j] > k:
				chng = False
				break
		if chng:
			union(i, j)
for i in range(n):
	par(i)
Cp = Counter(p)
for g in Cp:
	ans *= f[Cp[g]]
	ans %= mod
print(ans)


class UnionFind:

	def __init__(self, n):
		self.n = n
		self.parents = [-1] * n
		self.ans = n * (n - 1) // 2

	def find(self, x):
		if self.parents[x] < 0:
			return x
		else:
			self.parents[x] = self.find(self.parents[x])
			return self.parents[x]

	def union(self, x, y):
		x = self.find(x)
		y = self.find(y)
		if x == y:
			return
		if self.parents[x] > self.parents[y]:
			(x, y) = (y, x)
		sx = self.size(x)
		sy = self.size(y)
		self.ans += sx * (sx - 1) // 2 + sy * (sy - 1) // 2
		self.ans -= (sx + sy) * (sx + sy - 1) // 2
		self.parents[x] += self.parents[y]
		self.parents[y] = x

	def size(self, x):
		return -self.parents[self.find(x)]

	def same(self, x, y):
		return self.find(x) == self.find(y)

	def members(self, x):
		root = self.find(x)
		return [i for i in range(self.n) if self.find(i) == root]

	def roots(self):
		return [i for (i, x) in enumerate(self.parents) if x < 0]

	def group_count(self):
		return len(self.roots())

	def all_group_members(self):
		return {r: self.members(r) for r in self.roots()}

	def __str__(self):
		return '\n'.join(('{}: {}'.format(r, self.members(r)) for r in self.roots()))
mod = 998244353
pr = [1]
for i in range(1, 50):
	pr.append(pr[-1] * i % mod)
(n, k) = list(map(int, input().split()))
m = [list(map(int, input().split())) for _ in range(n)]
ufs = [UnionFind(n) for _ in range(2)]
for x in range(n):
	for y in range(n):
		fg = True
		for i in range(n):
			if m[i][x] + m[i][y] > k:
				fg = False
				break
		if fg:
			ufs[0].union(x, y)
		fg = True
		for i in range(n):
			if m[x][i] + m[y][i] > k:
				fg = False
				break
		if fg:
			ufs[1].union(x, y)
ret = 1
for uf in ufs:
	for r in uf.roots():
		ret *= pr[uf.size(r)]
		ret %= mod
print(ret)


import numpy as np

class UnionFind:

	def __init__(self, n):
		self.n = n
		self.parents = [-1] * n

	def find(self, x):
		if self.parents[x] < 0:
			return x
		else:
			self.parents[x] = self.find(self.parents[x])
			return self.parents[x]

	def union(self, x, y):
		x = self.find(x)
		y = self.find(y)
		if x == y:
			return
		if self.parents[x] > self.parents[y]:
			(x, y) = (y, x)
		self.parents[x] += self.parents[y]
		self.parents[y] = x

	def size(self, x):
		return -self.parents[self.find(x)]

	def same(self, x, y):
		return self.find(x) == self.find(y)

	def members(self, x):
		root = self.find(x)
		return [i for i in range(self.n) if self.find(i) == root]

	def roots(self):
		return [i for (i, x) in enumerate(self.parents) if x < 0]

	def all_group_members(self):
		return {r: self.members(r) for r in self.roots()}
mod = 998244353
(n, k) = map(int, input().split())
a = np.array([list(map(int, input().split())) for _ in range(n)], dtype=np.int64)
ans = 1
for _ in range(2):
	uf = UnionFind(n)
	for i in range(n):
		for j in range(i, n):
			if i == j:
				continue
			if max(a[i] + a[j]) <= k:
				uf.union(i, j)
	c = uf.all_group_members()
	for (key, v) in c.items():
		tmp = 1
		for i in range(len(v)):
			tmp *= i + 1
			tmp %= mod
		ans *= tmp
		ans %= mod
	a = a[::-1].T
print(ans)


import sys
sys.setrecursionlimit(10 ** 9)
INF = 10 ** 18
MOD = 998244353
input = lambda : sys.stdin.readline().rstrip()
YesNo = lambda b: bool([print('Yes')] if b else print('No'))
YESNO = lambda b: bool([print('YES')] if b else print('NO'))
int1 = lambda x: int(x) - 1
(N, K) = map(int, input().split())
a = [list(map(int, input().split())) for _ in range(N)]

class UnionFind:

	def __init__(self, n):
		self.par = [i for i in range(n)]
		self.siz = [1] * n

	def root(self, x):
		while self.par[x] != x:
			self.par[x] = self.par[self.par[x]]
			x = self.par[x]
		return x

	def unite(self, x, y):
		x = self.root(x)
		y = self.root(y)
		if x == y:
			return False
		if self.siz[x] < self.siz[y]:
			(x, y) = (y, x)
		self.siz[x] += self.siz[y]
		self.par[y] = x
		return True

	def is_same(self, x, y):
		return self.root(x) == self.root(y)

	def size(self, x):
		return self.siz[self.root(x)]

	def get_roots(self):
		roots = set()
		for i in range(len(self.par)):
			roots.add(self.root(i))
		return list(roots)

	def get_roots_and_size(self):
		roots = self.get_roots()
		roots_and_size = [[v, self.size(v)] for v in roots]
		return roots_and_size
uy = UnionFind(N)
ux = UnionFind(N)
for y0 in range(N):
	for y1 in range(N):
		for x in range(N):
			if a[y0][x] + a[y1][x] > K:
				break
		else:
			uy.unite(y0, y1)
for x0 in range(N):
	for x1 in range(N):
		for y in range(N):
			if a[y][x0] + a[y][x1] > K:
				break
		else:
			ux.unite(x0, x1)
ans = 0
ry = uy.get_roots_and_size()
rx = ux.get_roots_and_size()

def COMinit(n, MOD):
	(fac, finv, inv) = ([0] * max(2, n + 1), [0] * max(2, n + 1), [0] * max(2, n + 1))
	fac[0] = fac[1] = 1
	finv[0] = finv[1] = 1
	inv[1] = 1
	for i in range(2, n + 1):
		fac[i] = fac[i - 1] * i % MOD
		inv[i] = MOD - inv[MOD % i] * (MOD // i) % MOD
		finv[i] = finv[i - 1] * inv[i] % MOD
	return (fac, finv, inv)
(fac, finv, inv) = COMinit(N, MOD)

def COM(n, k, MOD=MOD):
	if n < k or n < 0 or k < 0:
		return 0
	return fac[n] * (finv[k] * finv[n - k] % MOD) % MOD
ans = 1
for (_, v) in ry:
	ans *= fac[v]
	ans %= MOD
for (_, v) in rx:
	ans *= fac[v]
	ans %= MOD
print(ans)


from collections import Counter
(n, k) = map(int, input().split())
a = [list(map(int, input().split())) for _ in range(n)]
MOD = 998244353
lst1 = [[] for _ in range(n)]
lst2 = [[] for _ in range(n)]
g1 = [-1] * n
g2 = [-1] * n
for i in range(n - 1):
	for j in range(i + 1, n):
		jdg1 = True
		jdg2 = True
		for l in range(n):
			if a[i][l] + a[j][l] > k:
				jdg1 = False
			if a[l][i] + a[l][j] > k:
				jdg2 = False
			if not jdg1 and (not jdg2):
				break
		if jdg1:
			lst1[i].append(j)
			lst1[j].append(i)
			g1[i] = 0
			g1[j] = 0
		if jdg2:
			lst2[i].append(j)
			lst2[j].append(i)
			g2[i] = 0
			g2[j] = 0

def calc(lst, g):
	cnt = 1
	for i in range(n):
		if g[i] != 0:
			continue
		temp = [i]
		g[i] = cnt
		while temp:
			p = temp.pop()
			for ip in lst[p]:
				if g[ip] != 0:
					continue
				g[ip] = cnt
				temp.append(ip)
		cnt += 1
	return g
g1 = calc(lst1, g1)
cnt1 = Counter(g1)
g2 = calc(lst2, g2)
cnt2 = Counter(g2)
ans = 1
for (i, v) in cnt1.items():
	if i == -1:
		continue
	for j in range(1, v + 1):
		ans *= j % MOD
	ans %= MOD
for (i, v) in cnt2.items():
	if i == -1:
		continue
	for j in range(1, v + 1):
		ans *= j % MOD
	ans %= MOD
print(ans)


import itertools, math
from collections import defaultdict

class UnionFind:

	def __init__(self, n):
		self.n = n
		self.parents = [-1] * n

	def find(self, x):
		if self.parents[x] < 0:
			return x
		else:
			self.parents[x] = self.find(self.parents[x])
			return self.parents[x]

	def union(self, x, y):
		x = self.find(x)
		y = self.find(y)
		if x == y:
			return
		if self.parents[x] > self.parents[y]:
			(x, y) = (y, x)
		self.parents[x] += self.parents[y]
		self.parents[y] = x

	def size(self, x):
		return -self.parents[self.find(x)]

	def same(self, x, y):
		return self.find(x) == self.find(y)

	def members(self, x):
		root = self.find(x)
		return [i for i in range(self.n) if self.find(i) == root]

	def roots(self):
		return [i for (i, x) in enumerate(self.parents) if x < 0]

	def group_count(self):
		return len(self.roots())

	def all_group_members(self):
		group_members = defaultdict(list)
		for member in range(self.n):
			group_members[self.find(member)].append(member)
		return group_members

	def __str__(self):
		return '\n'.join((f'{r}: {m}' for (r, m) in list(self.all_group_members().items())))
(n, k) = list(map(int, input().split()))
a = [list(map(int, input().split())) for i in range(n)]
a_row = []
a_column = []
for i in itertools.combinations(list(range(n)), 2):
	for j in range(n):
		if a[i[0]][j] + a[i[1]][j] > k:
			break
		if j == n - 1:
			a_row.append(i)
	for j in range(n):
		if a[j][i[0]] + a[j][i[1]] > k:
			break
		if j == n - 1:
			a_column.append(i)
uf_row = UnionFind(n)
uf_column = UnionFind(n)
for i in a_row:
	uf_row.union(i[0], i[1])
for i in a_column:
	uf_column.union(i[0], i[1])
ans = 1
for i in list(uf_row.all_group_members().values()):
	ans = ans * math.factorial(len(i)) % 998244353
for i in list(uf_column.all_group_members().values()):
	ans = ans * math.factorial(len(i)) % 998244353
print(ans)


(n, k) = map(int, input().split())
mat = []
for i in range(n):
	mat.append(list(map(int, input().split())))

def fact(n):
	tmp = 1
	for i in range(2, n + 1):
		tmp *= i
		tmp %= 998244353
	return tmp
goto = [[] for i in range(n)]
for gyou1 in range(n):
	for gyou2 in range(gyou1 + 1, n):
		OK = True
		for retu in range(n):
			if mat[gyou1][retu] + mat[gyou2][retu] > k:
				OK = False
		if OK:
			goto[gyou1].append(gyou2)
			goto[gyou2].append(gyou1)
from collections import deque
que = deque()
gro = [-1] * n
que.append(0)
gro_no = 0
gro[0] = 0
for i in range(n):
	if gro[i] == -1:
		que.append(i)
		gro_no += 1
		gro[i] = gro_no
	while que:
		now = que.popleft()
		for j in goto[now]:
			if gro[j] == -1:
				que.append(j)
				gro[j] = gro_no
gro_cnt = [0] * (gro_no + 1)
for i in range(n):
	gro_cnt[gro[i]] += 1
ans_gyou = 1
for i in range(gro_no + 1):
	ans_gyou *= fact(gro_cnt[i])
goto = [[] for i in range(n)]
for retu1 in range(n):
	for retu2 in range(retu1 + 1, n):
		OK = True
		for gyou in range(n):
			if mat[gyou][retu1] + mat[gyou][retu2] > k:
				OK = False
		if OK:
			goto[retu1].append(retu2)
			goto[retu2].append(retu1)
from collections import deque
que = deque()
gro = [-1] * n
que.append(0)
gro_no = 0
gro[0] = 0
for i in range(n):
	if gro[i] == -1:
		que.append(i)
		gro_no += 1
		gro[i] = gro_no
	while que:
		now = que.popleft()
		for j in goto[now]:
			if gro[j] == -1:
				que.append(j)
				gro[j] = gro_no
gro_cnt = [0] * (gro_no + 1)
for i in range(n):
	gro_cnt[gro[i]] += 1
ans_retu = 1
for i in range(gro_no + 1):
	ans_retu *= fact(gro_cnt[i])
ans = ans_gyou * ans_retu % 998244353
print(ans)


class UnionFindTree:

	def __init__(self, n):
		self.par = [-1] * n

	def find(self, x):
		if self.par[x] < 0:
			return x
		else:
			self.par[x] = self.find(self.par[x])
			return self.par[x]

	def union(self, x, y):
		x = self.find(x)
		y = self.find(y)
		if x == y:
			return
		if self.par[x] > self.par[y]:
			(x, y) = (y, x)
		self.par[x] += self.par[y]
		self.par[y] = x

	def same(self, x, y):
		return self.find(x) == self.find(y)

	def size(self, x):
		return -self.par[self.find(x)]
(N, K) = map(int, input().split())
a = [list(map(int, input().split())) for _ in range(N)]
mod = 998244353
uf1 = UnionFindTree(N)
uf2 = UnionFindTree(N)
for i in range(N):
	for j in range(i + 1, N):
		flag = True
		for k in range(N):
			if a[i][k] + a[j][k] > K:
				flag = False
		if flag:
			uf1.union(i, j)
for i in range(N):
	for j in range(i + 1, N):
		flag = True
		for k in range(N):
			if a[k][i] + a[k][j] > K:
				flag = False
		if flag:
			uf2.union(i, j)
l = [1]
for i in range(1, N + 1):
	l.append(l[-1] * i % mod)
res = 1
for i in range(N):
	if i == uf1.find(i):
		res = res * l[uf1.size(i)] % mod
	if i == uf2.find(i):
		res = res * l[uf2.size(i)] % mod
print(res)


import sys
readline = sys.stdin.readline
(N, K) = map(int, readline().split())
DIV = 998244353
A = [list(map(int, readline().split())) for i in range(N)]

class UnionFind:
	N = 0
	parent = None
	size = None

	def __init__(self, N):
		self.N = N
		self.parent = [i for i in range(self.N)]
		self.size = [1] * self.N

	def root(self, x):
		while x != self.parent[x]:
			self.parent[x] = self.parent[self.parent[x]]
			x = self.parent[x]
		return x

	def same(self, x, y):
		return self.root(x) == self.root(y)

	def unite(self, x, y):
		x = self.root(x)
		y = self.root(y)
		if x == y:
			return
		if self.size[x] > self.size[y]:
			self.parent[y] = x
			self.size[x] += self.size[y]
		else:
			self.parent[x] = y
			self.size[y] += self.size[x]

	def get_group_size(self, x):
		return self.size[self.root(x)]

	def get_roots(self):
		r = set()
		for i in range(self.N):
			r.add(self.root(i))
		return r

	def show_parent(self):
		print(self.parent)

	def show_size(self):
		print(self.size)
UF_row = UnionFind(N)
for i in range(N - 1):
	for j in range(i + 1, N):
		for k in range(N):
			if A[i][k] + A[j][k] > K:
				break
		else:
			UF_row.unite(i, j)
row_pat = 1
row_roots = UF_row.get_roots()
for root in row_roots:
	siz = UF_row.get_group_size(root)
	pat = 1
	for p in range(1, siz + 1):
		pat *= p
		pat %= DIV
	row_pat *= pat
	row_pat %= DIV
UF_col = UnionFind(N)
for i in range(N - 1):
	for j in range(i + 1, N):
		for k in range(N):
			if A[k][i] + A[k][j] > K:
				break
		else:
			UF_col.unite(i, j)
col_pat = 1
col_roots = UF_col.get_roots()
for root in col_roots:
	siz = UF_col.get_group_size(root)
	pat = 1
	for p in range(1, siz + 1):
		pat *= p
		pat %= DIV
	col_pat *= pat
	col_pat %= DIV
print(row_pat * col_pat % DIV)


MOD = 998244353
from math import factorial
(N, K) = list(map(int, input().split()))
field = [list(map(int, input().split())) for i in range(N)]

class Union_Find:

	def __init__(self, N):
		self.parent = [-1] * N
		self.rank = [0] * N
		self.group_count = N
		self.N = N

	def find(self, x):
		if self.parent[x] < 0:
			return x
		par = self.find(self.parent[x])
		self.parent[x] = par
		return par

	def unite(self, x, y):
		x = self.find(x)
		y = self.find(y)
		if x == y:
			return
		if self.rank[x] == self.rank[y]:
			self.parent[x] += self.parent[y]
			self.parent[y] = x
			self.rank[x] += 1
		elif self.rank[x] > self.rank[y]:
			self.parent[x] += self.parent[y]
			self.parent[y] = x
		else:
			self.parent[y] += self.parent[x]
			self.parent[x] = y
		self.group_count -= 1

	def samep(self, x, y):
		return self.find(x) == self.find(y)

	def get_group_member_list(self, x):
		x = self.find(x)
		return [i for i in range(self.N) if self.find(i) == x]

	def get_group_member_count(self, x):
		x = self.find(x)
		return -self.parent[x]

	def get_all_groups(self):
		return {idx: -n for (idx, n) in enumerate(self.parent) if n < 0}
hg = Union_Find(N)
for i in range(N - 1):
	h1 = field[i]
	for j in range(i, N):
		h2 = field[j]
		if all([True if x + y <= K else False for (x, y) in zip(h1, h2)]):
			hg.unite(i, j)
vg = Union_Find(N)
rotated = list(zip(*field))
for i in range(N - 1):
	v1 = rotated[i]
	for j in range(i, N):
		v2 = rotated[j]
		if all([True if x + y <= K else False for (x, y) in zip(v1, v2)]):
			vg.unite(i, j)
result = 1
for value in list(hg.get_all_groups().values()):
	result *= factorial(value)
	result %= MOD
for value in list(vg.get_all_groups().values()):
	result *= factorial(value)
	result %= MOD
print(result)


import math, itertools, fractions, heapq, collections, bisect, sys, queue, copy
sys.setrecursionlimit(10 ** 7)
inf = 10 ** 20
mod = 998244353
dd = [(-1, 0), (0, 1), (1, 0), (0, -1)]
ddn = [(-1, 0), (-1, 1), (0, 1), (1, 1), (1, 0), (1, -1), (0, -1), (-1, -1)]

def LI():
	return [int(x) for x in sys.stdin.readline().split()]

def I():
	return int(sys.stdin.readline())

def F():
	return float(sys.stdin.readline())

def LS():
	return sys.stdin.readline().split()

def S():
	return input()

class UnionFind:

	def __init__(self, sz):
		self.sz = sz
		self.data = [-1] * sz
		self.amount = [0] * sz

	def unite(self, x, y):
		x = self.find(x)
		y = self.find(y)
		if x == y:
			return False
		self.amount[x] += self.amount[y]
		self.amount[y] += self.amount[x]
		if self.data[x] > self.data[y]:
			(x, y) = (y, x)
		self.data[x] += self.data[y]
		self.data[y] = x
		return True

	def find(self, k):
		if self.data[k] < 0:
			return k
		self.data[k] = self.find(self.data[k])
		return self.data[k]

	def size(self, k):
		return -self.data[self.find(k)]

	def set_amount(self, k, k_amount):
		self.amount[k] = k_amount

	def get_amount(self, k):
		return self.amount[k]

def main():
	(n, K) = LI()
	field = [LI() for _ in range(n)]
	uf1 = UnionFind(n)
	uf2 = UnionFind(n)
	for i in range(n):
		l1 = [True] * n
		for j in range(i + 1, n):
			if not l1[i]:
				continue
			for k in range(n):
				if field[i][k] + field[j][k] > K:
					l1[j] = False
					break
		for j in range(i + 1, n):
			if l1[j]:
				uf1.unite(i, j)
	for i in range(n):
		l2 = [True] * n
		for j in range(i + 1, n):
			if not l2[i]:
				continue
			for k in range(n):
				if field[k][i] + field[k][j] > K:
					l2[j] = False
					break
		for j in range(i + 1, n):
			if l2[j]:
				uf2.unite(i, j)
	ans1 = 1
	check1 = [True] * n
	for i in range(n):
		sz = uf1.size(i)
		pa = uf1.find(i)
		if check1[pa]:
			check1[pa] = False
			_ans = 1
			for j in range(2, sz + 1):
				_ans *= j
				_ans %= mod
			ans1 *= _ans
			ans1 %= mod
	ans2 = 1
	check2 = [True] * n
	for i in range(n):
		sz = uf2.size(i)
		pa = uf2.find(i)
		if check2[pa]:
			check2[pa] = False
			_ans = 1
			for j in range(2, sz + 1):
				_ans *= j
				_ans %= mod
			ans2 *= _ans
			ans2 %= mod
	return ans1 * ans2 % mod
print(main())


import sys
try:
	import os
	f = open('input.txt', 'r')
	sys.stdin = f
except FileNotFoundError:
	None
from math import sqrt, ceil, floor
from collections import deque, Counter, defaultdict
input = lambda : sys.stdin.readline().strip()
sys.setrecursionlimit(11451419)
from decimal import ROUND_HALF_UP, Decimal
from functools import lru_cache
from bisect import bisect_left as bileft, bisect_right as biright, insort
from itertools import combinations as com, permutations as per
from fractions import Fraction as frac
(n, k) = list(map(int, input().split()))
A = [list(map(int, input().split())) for i in range(n)]
cntx = 0
cnty = 0
kai = [1] * 70
mod = 998244353
for i in range(1, 65):
	kai[i] = kai[i - 1] * i % mod

class UnionFind:

	def __init__(self, num):
		self.n = num
		self.parents = [-1 for i in range(self.n)]

	def find(self, x):
		if self.parents[x] < 0:
			return x
		else:
			self.parents[x] = self.find(self.parents[x])
			return self.parents[x]

	def union(self, x, y):
		xx = self.find(x)
		yy = self.find(y)
		if xx == yy:
			return
		else:
			size_xx = abs(self.parents[xx])
			size_yy = abs(self.parents[yy])
			if size_xx > size_yy:
				(xx, yy) = (yy, xx)
			self.parents[yy] += self.parents[xx]
			self.parents[xx] = yy

	def size(self, x):
		xx = self.find(x)
		return abs(self.parents[xx])

	def same(self, x, y):
		return 1 if self.find(x) == self.find(y) else 0

	def members(self, x):
		xx = self.find(x)
		return [i for i in range(self.n) if self.find(i) == xx]

	def roots(self):
		return [i for (i, x) in enumerate(self.parents) if x < 0]

	def all_group_members(self):
		return {r: self.members(r) for r in self.roots()}

	def state_grouping(self):
		return list(self.all_group_members().values())
ufx = UnionFind(n)
ufy = UnionFind(n)
for i in range(n):
	po = 0
	for j in range(i):
		for p in range(n):
			if A[i][p] + A[j][p] > k:
				po = 1
				break
		else:
			ufx.union(i, j)
for i in range(n):
	for j in range(n):
		for p in range(n):
			if A[p][i] + A[p][j] > k:
				break
		else:
			ufy.union(i, j)
xx = 1
yy = 1
for i in ufx.parents:
	if i < -1:
		xx *= kai[abs(i)]
		xx %= mod
for j in ufy.parents:
	if j < -1:
		yy *= kai[abs(j)]
		yy %= mod
print(xx * yy % mod)


class UnionFind:

	def __init__(self, n):
		self.n = n
		self.parents = [-1] * n

	def find(self, x):
		if self.parents[x] < 0:
			return x
		else:
			self.parents[x] = self.find(self.parents[x])
			return self.parents[x]

	def union(self, x, y):
		x = self.find(x)
		y = self.find(y)
		if x == y:
			return
		if self.parents[x] > self.parents[y]:
			(x, y) = (y, x)
		self.parents[x] += self.parents[y]
		self.parents[y] = x

	def size(self, x):
		return -self.parents[self.find(x)]

	def same(self, x, y):
		return self.find(x) == self.find(y)

	def members(self, x):
		root = self.find(x)
		return [i for i in range(self.n) if self.find(i) == root]

	def roots(self):
		return [i for (i, x) in enumerate(self.parents) if x < 0]

	def group_count(self):
		return len(self.roots())

	def __str__(self):
		return '\n'.join(('{}: {}'.format(r, self.members(r)) for r in self.roots()))
mod = 998244353
(n, wa) = map(int, input().split())
a = [list(map(int, input().split())) for i in range(n)]
ufg = UnionFind(n)
ufl = UnionFind(n)
for i in range(n - 1):
	for j in range(i + 1, n):
		fl = 1
		fg = 1
		for k in range(n):
			if a[i][k] + a[j][k] > wa:
				fg = 0
				break
		for k in range(n):
			if a[k][i] + a[k][j] > wa:
				fl = 0
				break
		if fg:
			ufg.union(i, j)
		if fl:
			ufl.union(i, j)
from math import factorial as fa
sg = 1
sl = 1
for i in ufg.roots():
	sg *= fa(ufg.size(i))
	sg %= mod
for i in ufl.roots():
	sl *= fa(ufl.size(i))
	sl %= mod
print(sl * sg % mod)


class UnionFind:

	def __init__(self, n):
		self.n = n
		self.parents = [-1] * n

	def find(self, x):
		if self.parents[x] < 0:
			return x
		else:
			self.parents[x] = self.find(self.parents[x])
			return self.parents[x]

	def union(self, x, y):
		x = self.find(x)
		y = self.find(y)
		if x == y:
			return
		if self.parents[x] > self.parents[y]:
			(x, y) = (y, x)
		self.parents[x] += self.parents[y]
		self.parents[y] = x

	def size(self, x):
		return -self.parents[self.find(x)]

	def same(self, x, y):
		return self.find(x) == self.find(y)

	def members(self, x):
		root = self.find(x)
		return [i for i in range(self.n) if self.find(i) == root]

	def roots(self):
		return [i for (i, x) in enumerate(self.parents) if x < 0]

	def group_count(self):
		return len(self.roots())

	def all_group_members(self):
		return {r: self.members(r) for r in self.roots()}

	def __str__(self):
		return '\n'.join(('{}: {}'.format(r, self.members(r)) for r in self.roots()))
(N, K) = map(int, input().split())
A = []
for i in range(N):
	a = list(map(int, input().split()))
	A.append(a)
UF1 = UnionFind(N)
UF2 = UnionFind(N)
for i in range(N):
	for j in range(i + 1, N):
		l = 0
		for k in range(N):
			if A[i][k] + A[j][k] > K:
				l = 1
		if l == 0:
			UF1.union(i, j)
for i in range(N):
	for j in range(i + 1, N):
		l = 0
		for k in range(N):
			if A[k][i] + A[k][j] > K:
				l = 1
		if l == 0:
			UF2.union(i, j)

def fac(x):
	ans = 1
	for i in range(1, x + 1):
		ans *= i
		ans %= 998244353
	return ans
ans = 1
for i in UF1.parents:
	if i < 0:
		ans *= fac(-i)
		ans %= 998244353
for i in UF2.parents:
	if i < 0:
		ans *= fac(-i)
		ans %= 998244353
print(ans)


mod = 998244353
fact = [1]
for i in range(1, 51):
	x = i * fact[-1] % mod
	fact.append(x)
(n, k) = list(map(int, input().split()))

def dfs(node):
	if vis[node]:
		return 0
	vis[node] = 1
	ans = 1
	for i in m[node]:
		ans += dfs(i)
	return ans
l = []
for i in range(n):
	l.append(list(map(int, input().split())))
m = []
for i in range(n):
	m.append([])
for i in range(n):
	for j in range(i + 1, n):
		e = 1
		for b in range(n):
			if l[i][b] + l[j][b] > k:
				e = 0
				break
		if e:
			m[i].append(j)
			m[j].append(i)
a = 1
b = 1
vis = [0] * n
for i in range(n):
	x = dfs(i)
	a = a * fact[x] % mod
m = []
for i in range(n):
	m.append([])
for i in range(n):
	for j in range(i + 1, n):
		e = 1
		for v in range(n):
			if l[v][i] + l[v][j] > k:
				e = 0
				break
		if e:
			m[i].append(j)
			m[j].append(i)
vis = [0] * n
for i in range(n):
	x = dfs(i)
	b = b * fact[x] % mod
ans = a * b % mod
print(ans)


import math
from math import gcd, pi, sqrt
INF = float('inf')
MOD = 10 ** 9 + 7
import sys
input = sys.stdin.readline
sys.setrecursionlimit(10 ** 6)
import itertools
import bisect
import re
from collections import Counter, deque, defaultdict

def iinput():
	return int(input())

def imap():
	return map(int, input().split())

def ilist():
	return list(imap())

def irow(N):
	return [iinput() for i in range(N)]

def sinput():
	return input().rstrip()

def smap():
	return sinput().split()

def slist():
	return list(smap())

def srow(N):
	return [sinput() for i in range(N)]

def main():
	MOD = 998244353

	class UnionFind:

		def __init__(self, n):
			self.par = [i for i in range(n + 1)]
			self.rank = [0] * (n + 1)
			self.size = [1] * (n + 1)

		def find(self, x):
			if self.par[x] == x:
				return x
			else:
				self.par[x] = self.find(self.par[x])
				return self.par[x]

		def union(self, x, y):
			x = self.find(x)
			y = self.find(y)
			if x == y:
				return
			if self.rank[x] < self.rank[y]:
				self.par[x] = y
				self.size[y] += self.size[x]
				self.size[x] = 0
			else:
				self.par[y] = x
				self.size[x] += self.size[y]
				self.size[y] = 0
				if self.rank[x] == self.rank[y]:
					self.rank[x] += 1

		def same(self, x, y):
			return self.find(x) == self.find(y)

		def all_find(self):
			for n in range(len(self.par)):
				self.find(n)
	(N, K) = imap()
	import numpy as np
	A = np.array([ilist() for i in range(N)])
	uf_i = UnionFind(N)
	uf_r = UnionFind(N)
	for i in range(N - 1):
		for j in range(i + 1, N):
			if np.all(A[i] + A[j] <= K):
				uf_i.union(i + 1, j + 1)
			if np.all(A[:, i] + A[:, j] <= K):
				uf_r.union(i + 1, j + 1)
	uf_i.all_find()
	uf_r.all_find()
	ans = 1
	for i in uf_i.size:
		if i in [0, 1]:
			continue
		ans *= math.factorial(i)
	for j in uf_r.size:
		if j in [0, 1]:
			continue
		ans *= math.factorial(j)
	print(ans % MOD)

def __starting_point():
	main()
__starting_point()


mod = 998244353
(N, K) = list(map(int, input().split()))
A = []
for i in range(N):
	a = [int(i) for i in input().split()]
	A.append(a)
memo = [1]
for i in range(1, 52):
	num = memo[-1]
	memo.append(num * i % mod)

class UnionFind:

	def __init__(self, n):
		self.table = [-1] * n

	def _root(self, x):
		stack = []
		tbl = self.table
		while tbl[x] >= 0:
			stack.append(x)
			x = tbl[x]
		for y in stack:
			tbl[y] = x
		return x

	def count(self, x):
		return -self.table[self._root(x)]

	def find(self, x, y):
		return self._root(x) == self._root(y)

	def union(self, x, y):
		r1 = self._root(x)
		r2 = self._root(y)
		if r1 == r2:
			return
		d1 = self.table[r1]
		d2 = self.table[r2]
		if d1 <= d2:
			self.table[r2] = r1
			self.table[r1] += d2
		else:
			self.table[r1] = r2
			self.table[r2] += d1
uni1 = UnionFind(N)
uni2 = UnionFind(N)
for i in range(N - 1):
	for j in range(i + 1, N):
		cur1 = 0
		cur2 = 0
		for k in range(N):
			if A[i][k] + A[j][k] <= K:
				cur1 += 1
			if A[k][i] + A[k][j] <= K:
				cur2 += 1
		if cur1 == N:
			uni1.union(i, j)
		if cur2 == N:
			uni2.union(i, j)
lsnum = []
(set1, set2) = (set(), set())
for i in range(N):
	if uni1._root(i) not in set1:
		lsnum.append(uni1.count(i))
		set1.add(uni1._root(i))
	if uni2._root(i) not in set2:
		lsnum.append(uni2.count(i))
		set2.add(uni2._root(i))
ans = 1
for num in lsnum:
	ans = ans * memo[num] % mod
ans %= mod
print(ans)


import collections as co

def searchRoot(n):
	par = parList[n]
	if n == par:
		if len(q) > 0:
			rootIn(q, n)
		return n
	else:
		q.append(n)
		return searchRoot(par)

def rootIn(q, root):
	while len(q) > 0:
		parList[q.popleft()] = root

def UnionFind(n, m, lis):
	for i in range(m):
		(a, b) = lis[i]
		aRoot = searchRoot(a)
		bRoot = searchRoot(b)
		if aRoot != bRoot:
			parList[max(aRoot, bRoot)] = min(aRoot, bRoot)
	ansDic = dict()
	rootSet = set()
	for i in range(0, n):
		root = searchRoot(i)
		if root in rootSet:
			ansDic[root] += 1
		else:
			rootSet.add(root)
			ansDic[root] = 1
	return ansDic
(n, k) = list(map(int, input().split()))
a = []
for i in range(n):
	aRow = list(map(int, input().split()))
	a.append(aRow)
rowLis = []
for i in range(n - 1):
	for j in range(i + 1, n):
		check = True
		for l in range(n):
			if a[i][l] + a[j][l] > k:
				check = False
				break
		if check:
			rowLis.append([i, j])
parList = [i for i in range(n + 1)]
q = co.deque()
rowDic = UnionFind(n, len(rowLis), rowLis)
colLis = []
for i in range(n - 1):
	for j in range(i + 1, n):
		check = True
		for l in range(n):
			if a[l][i] + a[l][j] > k:
				check = False
				break
		if check:
			colLis.append([i, j])
parList = [i for i in range(n + 1)]
q = co.deque()
colDic = UnionFind(n, len(colLis), colLis)
ans = 1
for i in rowDic.values():
	for j in range(1, i + 1):
		ans = ans * j
for i in colDic.values():
	for j in range(1, i + 1):
		ans = ans * j
ans = ans % 998244353
print(ans)


MOD = 998244353
SIZE = 10 ** 6
g1 = [1, 1]
g2 = [1, 1]
re = [0, 1]
for i in range(2, SIZE + 1):
	g1.append(g1[-1] * i % MOD)

def fact(n):
	return g1[n]

class UnionFind:

	def __init__(self, n):
		self.root = [i for i in range(n + 1)]
		self.size = [1] * (n + 1)
		self.nn = n

	def find(self, x):
		y = self.root[x]
		if x == y:
			return x
		else:
			z = self.find(y)
			self.root[x] = z
			return z

	def union(self, x, y):
		rx = self.find(x)
		ry = self.find(y)
		sx = self.size[rx]
		sy = self.size[ry]
		if rx == ry:
			return 0
		elif sx >= sy:
			self.root[ry] = rx
			self.size[rx] = sx + sy
		else:
			self.root[rx] = ry
			self.size[ry] = sx + sy
		return sx * sy

	def size_list(self, n):
		ret = []
		cnt = [0] * (n + 1)
		for i in range(1, n + 1):
			if cnt[self.find(i)] == 0:
				cnt[self.find(i)] = 1
				ret.append(self.size[self.find(i)])
		return ret

	def check(self):
		print([self.find(i) for i in range(1, self.nn + 1)])
(N, K) = map(int, input().split())
A = [list(map(int, input().split())) for _ in range(N)]

def f(A):
	edge = []
	for i in range(N):
		ai = A[i]
		for j in range(i + 1, N):
			aj = A[j]
			flag = True
			for k in range(N):
				if ai[k] + aj[k] > K:
					flag = False
					break
			if flag:
				edge.append([i + 1, j + 1])
	for (a, b) in edge:
		uf.union(a, b)
	ret = 1
	sl = uf.size_list(N)
	for n in sl:
		ret = ret * fact(n) % MOD
	return ret
uf = UnionFind(N)
answer = f(A) % MOD
tmp = [[] for _ in range(N)]
for i in range(N):
	for j in range(N):
		tmp[j].append(A[i][j])
A = tmp
uf = UnionFind(N)
answer = answer * f(A) % MOD
print(answer)