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: interview

Starter code:

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)


    #row

    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): # sizeをO(1)で引けるversion
    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): # 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,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): # x,yが連結かどうか
        return self.find(x) == self.find(y)
    def size(self,x): # xの属する連結sizeをO(1)で出す
        x = self.find(x)
        return self.sizebox[x]
    def allfind(self): # findを1周する
        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()]
        # print(A[n])
    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)
    # print(c)
    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)
    # print(c)
    for d in Counter(c).values():
        ans = (ans * factorial(d)) % MOD

    print(ans)

def __starting_point():
    main()
__starting_point()

class dsu():
    """Data structures and algorithms for disjoint set union problems.
    
    Given an undirected graph, it processes the following queries in O(alpha(n)) time (amortized).

    >   Edge addition
    
    >   Deciding whether given two vertices are in the same connected component

    Each connected component internally has a representative vertex.

    When two connected components are merged by edge addition, one of the two representatives of these connected components becomes the representative of the new connected component.
    """


    __slots__ = ["_n", "parent_or_size"]


    def __init__(self, n):
        """
        It creates an undirected graph with n vertices and 0 edges.
        
        Constraints
        -----------

        >   0 <= n <= 10 ** 8

        Complexity
        ----------

        >   O(n)
        """
        self._n = n
        self.parent_or_size = [-1] * n
    
    
    def merge(self, a, b):
        """
        It adds an edge (a, b).

        If the vertices a and b were in the same connected component, it returns the representative of this connected component. Otherwise, it returns the representative of the new connected component.
       
        Constraints
        -----------

        >   0 <= a < n 

        >   0 <= b < n
        
        Complexity
        ----------

        >   O(alpha(n)) amortized
        """
        # assert 0 <= a < self._n
        # assert 0 <= b < self._n
        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):
        """
        It returns whether the vertices a and b are in the same connected component.

        Constraints
        -----------

        >   0 <= a < n

        >   0 <= b < n

        Complexity
        ----------

        >   O(alpha(n)) amortized
        """
        # assert 0 <= a < self._n
        # assert 0 <= b < self._n
        return self.leader(a) == self.leader(b)


    def leader(self, a):
        """
        It returns the representative of the connected component that contains the vertex a.

        Constraints
        -----------

        >   0 <= a < n

        Complexity
        ----------

        >   O(alpha(n)) amortized
        """
        # assert 0 <= a < self._n
        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):
        """
        It returns the size of the connected component that contains the vertex aa.

        Constraints
        -----------

        >   0 <= a < n
        
        Complexity
        ----------

        >   O(alpha(n)) amortized
        """
        # assert 0 <= a < self._n
        return -self.parent_or_size[self.leader(a)]


    def groups(self):
        """
        It divides the graph into connected components and returns the list of them.

        More precisely, it returns the list of the "list of the vertices in a connected component". Both of the orders of the connected components and the vertices are undefined.

        Complexity
        ----------

        >   O(n)
        """
        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))



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): # sizeをO(1)で引けるversion
    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): # 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,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): # x,yが連結かどうか
        return self.find(x) == self.find(y)
    def size(self,x): # xの属する連結sizeをO(1)で出す
        x = self.find(x)
        return self.sizebox[x]
    def allfind(self): # findを1周する
        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)

for i in range(n):
    ufr.find(i)
    ufc.find(j)

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)

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):
        """
        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 と 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):
        """
        x と y が同じグループか否か
        """
        return self.find(x) == self.find(y)
    def get_size(self, x):
        """
        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)
#print(par0)

X = 1
for x in par0:
  temp = uf0.get_size(x)
  X = X*fac[temp]%MOD

A = A.T
#print(A)

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
#print(Y)

ans = X*Y%MOD
print(ans)

#!/usr/bin/env python3
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))

    # print(rows)
    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))

    # print(cols)
    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):#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とｙを併合させる
    x,y=self.find(x),self.find(y)#xとyの親の検索
    if x!=y:#親が異なる場合併合させる
      if x>y:
        x,y=y,x#小さい方をxとする. これにより要素の値が小さいものを優先して木の根とする. 
      self.par[x]+=self.par[y] #値を無向木の要素数の和にする.
      self.par[y]=x #枝側は根の位置を格納

  def same(self, x, y):#要素xと要素yが同じ無向木に所属しているかを判定する
    return self.find(x) == self.find(y)#同じ値を持つか否か

  def size(self, x):#要素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():
    """Data structures and algorithms for disjoint set union problems.
    
    Given an undirected graph, it processes the following queries in O(alpha(n)) time (amortized).

    >   Edge addition
    
    >   Deciding whether given two vertices are in the same connected component

    Each connected component internally has a representative vertex.

    When two connected components are merged by edge addition, one of the two representatives of these connected components becomes the representative of the new connected component.
    """


    __slots__ = ["_n", "parent_or_size"]


    def __init__(self, n):
        """
        It creates an undirected graph with n vertices and 0 edges.
        
        Constraints
        -----------

        >   0 <= n <= 10 ** 8

        Complexity
        ----------

        >   O(n)
        """
        self._n = n
        self.parent_or_size = [-1] * n
    
    
    def merge(self, a, b):
        """
        It adds an edge (a, b).

        If the vertices a and b were in the same connected component, it returns the representative of this connected component. Otherwise, it returns the representative of the new connected component.
       
        Constraints
        -----------

        >   0 <= a < n 

        >   0 <= b < n
        
        Complexity
        ----------

        >   O(alpha(n)) amortized
        """
        # assert 0 <= a < self._n
        # assert 0 <= b < self._n
        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):
        """
        It returns whether the vertices a and b are in the same connected component.

        Constraints
        -----------

        >   0 <= a < n

        >   0 <= b < n

        Complexity
        ----------

        >   O(alpha(n)) amortized
        """
        # assert 0 <= a < self._n
        # assert 0 <= b < self._n
        return self.leader(a) == self.leader(b)


    def leader(self, a):
        """
        It returns the representative of the connected component that contains the vertex a.

        Constraints
        -----------

        >   0 <= a < n

        Complexity
        ----------

        >   O(alpha(n)) amortized
        """
        # assert 0 <= a < self._n
        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):
        """
        It returns the size of the connected component that contains the vertex aa.

        Constraints
        -----------

        >   0 <= a < n
        
        Complexity
        ----------

        >   O(alpha(n)) amortized
        """
        # assert 0 <= a < self._n
        return -self.parent_or_size[self.leader(a)]


    def groups(self):
        """
        It divides the graph into connected components and returns the list of them.

        More precisely, it returns the list of the "list of the vertices in a connected component". Both of the orders of the connected components and the vertices are undefined.

        Complexity
        ----------

        >   O(n)
        """
        result = [[] for _ in range(self._n)]
        for i in range(self._n):
            result[self.leader(i)].append(i)
        return [g for g in result if g]



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))



class DSU:
    def __init__(self, N):
        # stores parent node of given vertex
        self.parents = list(range(N))
        # gives size of component w given node
        self.size = [1] * N

    # runtime: amoritized O(1)
    # returns root node
    def find(self, x):
        root = x
        # finds parent node at the end of the chain
        while self.parents[root] != root:
            root = self.parents[root]

        # optional: path compression
        while (x != root):
            nextx = self.parents[x]
            self.parents[x] = root
            x = nextx
        
        return root

    # runtime: amortized O(1) by combining union by size and path compression
    # unifies two components together (the ones containing x and y)
    def union(self, x, y):
        xr, yr = self.find(x), self.find(y)
        # if they're in same group, do nothing
        if xr == yr:
            return False

        if self.size[xr] > self.size[yr]:
            xr, yr = yr, xr
        # size of xr >= size of yr
        # smaller points to the larger
        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 collections import defaultdict, Counter, deque
# from heapq import heappop, heappush, heapify
# from functools import lru_cache, reduce
# import bisect
# from itertools import permutations, combinations, combinations_with_replacement
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())) )

# num of pairs you can swap
row_dsu = DSU(n)
col_dsu = DSU(n)

# find rows
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():
    #input data
    n,k = map(int,input().split())
    A = read_intgrid(n)

    mod = 998244353

    #solve
    ans = 1
    uf1 = UnionFind(n)
    uf2 = UnionFind(n)

    # fix by columns
    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)]
#print(l)
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])
#print(a)
        
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])
#print(b)

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)

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): # sizeをO(1)で引けるversion
    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): # 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,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]
    def same(self, x, y): # x,yが連結かどうか
        return self.find(x) == self.find(y)
    def size(self,x): # xの属する連結sizeをO(1)で出す
        x = self.find(x)
        return self.sizebox[x]
    def allfind(self): # findを1周する
        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)

for i in range(n):
    ufr.find(i)
    ufc.find(j)

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=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)]
# print(a)
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)
# print(retsu)
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

# print(retsu.par)
# print(gyo.par)

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
    
    #要素xが属するグループの根を返す
    def find(self, x):
        if self.parents[x] < 0:
            return x
        else:
            self.parents[x] = self.find(self.parents[x])
            return self.parents[x]

    #xのグループとyのグループを併合
    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

    #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)


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
        else:
            if 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)

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): # sizeをO(1)で引けるversion
    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): # 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,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): # x,yが連結かどうか
        return self.find(x) == self.find(y)
    def size(self,x): # xの属する連結sizeをO(1)で出す
        x = self.find(x)
        return self.sizebox[x]
    def allfind(self): # findを1周する
        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)

for i in range(n):
    ufr.find(i)
    ufc.find(i)

ckey=set(ufc.par)
rkey=set(ufr.par)

ans=1

for k in ckey:
    x=ufc.size(k)
    ans*=permmod(x,x,mod)
    ans%=mod

for k in rkey:
    x=ufr.size(k)
    ans*=permmod(x,x,mod)
    ans%=mod

print(ans%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):
        #assert 0 <= a < self.n
        #assert 0 <= b < self.n
        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):
        #assert 0 <= a < self.n
        #assert 0 <= b < self.n
        return self.leader(a) == self.leader(b)

    def leader(self, a):
        #assert 0 <= a < self.n
        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):
        #assert 0 <= a < self.n
        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)

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]

def union(x, y, uf, rank):
    px, py = find(x, uf), find(y, uf)
    if px != py:
        if rank[px] > rank[py]:
            px, py = py, px
        rank[py] += rank[px]
        uf[px] = py
  
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])):
      union(i, j, uf1, rank1)
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])):
      union(i, j, uf2, rank2)
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)


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:
  '''
  UnionFind(n) : n頂点0辺の無向グラフを構築する O(n)
    p:親ノード番号(代表元以外)、木のサイズの負数(代表元)を格納
  leader(a)    : aの属する代表元を返す O(α(n))
  merge(a,b)   : a,bを辺で結び、代表元を返す O(α(n))
  same(a,b)    : a,bが連結か否かを返す O(α(n))
  size(a)      : aの属する連結成分のサイズを返す O(α(n))
  count()      : グループ数を返す O(n)
  groups()     : グラフをsetにグループ分けして返す O(n)
  '''
  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:
    """
    0-indexed
    """

    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()

import itertools
import numpy as np
from networkx.utils import UnionFind

N, K = list(map(int, input().split()))
A = np.array([tuple(map(int, input().split())) for _ in range(N)])

MOD = 998244353
MAX = 50
factorial = [1] * (MAX + 1)
for i in range(1, MAX + 1):
    factorial[i] = factorial[i - 1] * i % MOD

uf_row = UnionFind(list(range(N)))
for i, j in itertools.combinations(list(range(N)), 2):
    if np.all(A[:, i] + A[:, j] <= K):
        uf_row.union(i, j)
uf_col = UnionFind(list(range(N)))
for i, j in itertools.combinations(list(range(N)), 2):
    if np.all(A[i] + A[j] <= K):
        uf_col.union(i, j)

row_res = 1
for group in uf_row.to_sets():
    row_res = row_res * factorial[len(group)] % MOD
col_res = 1
for group in uf_col.to_sets():
    col_res = col_res * factorial[len(group)] % MOD

print((row_res * col_res % MOD))


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() # swapable なcの組み合わせ数
r_pattern = list() # swapable なrの組み合わせ数

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
    # print(uf.roots())
    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
# rows and columns
for step in range(2):
    if step == 1:
        A = A.T

    uf = UnionFind(N)
    merge = uf.merge
    same = uf.same
    comb = 1

    # swap ok?
    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)

    # prod of tree-size!
    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
# input = sys.stdin.readline
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)])

#UnionFind
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):
        """ return (g, x, y) such that a*x + b*y = g = gcd(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
        """ n! (mod m) """
        if n >= self.mod:
            return 0
        self._make(n)
        return self._mint(self._factorial[n])

    def _make(self, n):
        """ Calc from self._size to n!^-1 : O(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):
        """ n!^-1 (mod m) """
        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):
        """ Calc r!^1 ... n!^-1 : O(n-r) """
        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):
        """ Return (gcd(a, b), x, y) such that a*x + b*y = gcd(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):
        """ n^-1 (mod m) """
        g, x, _ = self.xgcd(n, self.mod)
        assert g != 1
        return self._mint(x)

    def comb(self, n, r):
        """ nCr (mod m) """
        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):
        """ nCr (mod m) : O(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):
        """ nHr (mod m) """
        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):
        """ nPr (mod m) """
        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()

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]

def union(x, y, uf, rank):
    px, py = find(x, uf), find(y, uf)
    if px != py:
        if rank[px] > rank[py]:
            px, py = py, px
        rank[py] += rank[px]
        uf[px] = py
  
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])):
      union(i, j, uf1, rank1)
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])):
      union(i, j, uf2, rank2)
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)


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)

from itertools import combinations
from functools import partial


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):
        """ return (g, x, y) such that a*x + b*y = g = gcd(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
        """ n! (mod m) """
        if n >= self.mod:
            return 0
        self._make(n)
        return self._mint(self._factorial[n])

    def _make(self, n):
        """ Calc from self._size to n!^-1 : O(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):
        """ n!^-1 (mod m) """
        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):
        """ Calc r!^1 ... n!^-1 : O(n-r) """
        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):
        """ Return (gcd(a, b), x, y) such that a*x + b*y = gcd(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):
        """ n^-1 (mod m) """
        g, x, _ = self.xgcd(n, self.mod)
        assert g != 1
        return self._mint(x)

    def comb(self, n, r):
        """ nCr (mod m) """
        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):
        """ nCr (mod m) : O(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):
        """ nHr (mod m) """
        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):
        """ nPr (mod m) """
        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, y1 in combinations(range(n), 2):
    if all(A[y0][x] + A[y1][x] <= k for x in range(n)): 
        X.unite(y0, y1)

Y = UnionFind(n)
for x0, x1 in combinations(range(n), 2):
    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)

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)
# print(A_T)
# print(A)
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)
# print(row_uf.all_group_members())

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)
# print(column_uf.all_group_members())
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)]
#使えるUnionFind
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))


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)])
ans = 1
uf1 = UnionFind(N)
for i,j in itertools.combinations(list(range(N)),2):
    if (a[i,:]+a[j,:]<=K).all():
        uf1.union(i,j)
for x in uf1.parents:
    if x<=-2:
        for i in range(2,abs(x)+1):
            ans *= i
            ans %= 998244353
a = a.T
uf2 = UnionFind(N)
for i,j in itertools.combinations(list(range(N)),2):
    if (a[i,:]+a[j,:]<=K).all():
        uf2.union(i,j)
for x in uf2.parents:
    if x<=-2:
        for i in range(2,abs(x)+1):
            ans *= i
            ans %= 998244353
print(ans)


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)

# 1-index
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
        else:
            if 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)

# import sys
# sys.setrecursionlimit(10 ** 6)
# import bisect
# from collections import deque
inf = float('inf')
mod = 10 ** 9 + 7
mod2 = 998244353

class UnionFind:
    """
    下記から拝借
    https://note.nkmk.me/python-union-find/
    """

    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())


# from decorator import stop_watch
#
#
# @stop_watch
def solve(N, K, A):
    uf_row = UnionFind(N)
    uf_col = UnionFind(N)
    # grouping row
    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)
    # grouping col
    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)
    # calc ans
    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)

    # # test
    # from random import randint
    # import string
    # import tool.testcase as tt
    # from tool.testcase import random_str, random_ints
    # N, K = 50, 2 * 50 ** 2
    # A = [[N * i + j for j in range(1, N + 1)] for i 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)

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]

def union(x, y, uf, rank):
    px, py = find(x, uf), find(y, uf)
    if px != py:
        if rank[px] > rank[py]:
            px, py = py, px
        rank[py] += rank[px]
        uf[px] = py
  
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])):
      union(i, j, uf1, rank1)
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])):
      union(i, j, uf2, rank2)
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)


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)

#UnionFind
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)


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)



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):
        """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):
        """yをxの根に繋ぐ（マージテク有）"""
        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):
        """xとyが同じ連結成分か判別する"""
        return self.find(x) == self.find(y)

    def size(self, x):
        """xの連結成分の大きさを返す"""
        return -self.parents[self.find(x)]

    def kruskal(self, edge):
        """
        :param edge: edge = [(コスト, 頂点1, 頂点2),...]の形で重み付き隣接リストを渡して下さい
        :return: 最小全域木のコストの和
        """
        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()

#unionfind
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)


#!/usr/bin python3
# -*- coding: utf-8 -*-

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 sys

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


class UnionFind:
    """
    0-indexed
    """

    from typing import List

    def __init__(self, n):
        self.n = n
        self.parent = [-1] * 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 all_sizes(self) -> List[int]:
        sizes = []

        for i in range(self.n):
            size = self.parent[i]
            if size < 0:
                sizes.append(-size)
        return sizes

    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 size in uf.all_sizes():
        cnt_r *= factorial(size)
        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 size in uf.all_sizes():
        cnt_c *= factorial(size)
        cnt_c %= MOD

    print(((cnt_r * cnt_c) % MOD))


def __starting_point():
    main()

__starting_point()

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)

# -*- coding: utf-8 -*-
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:
    """Union-Find Tree

    Attributes:
        n (int):    頂点数
        par (list): 要素の格納先
    """
    def __init__(self, n):
        """初期化 O(1)

        Args:
            n (int): 頂点数
        """
        self.par = [-1] * n

    def find(self, x):
        """要素の検索 O(α(N))

        Args:
            x (int): 対象要素

        Returns:
            int: 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):
        """要素の併合 O(1)

        Args:
            x (int): 併合対象の集合に属する要素
            y (int): 併合対象の集合に属する要素
        """
        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):
        """要素の判定 O(1)

        Args:
            x (int): 判定対象の要素
            y (int): 判定対象の要素

        Returns:
            bool: xとyが同じ集合に属するか否か
        """
        return self.find(x) == self.find(y)
    
    def size(self, x):
        """要素数の計算 O(1)

        Args:
            x (int): 対象要素

        Returns:
            int: 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)]

# Kの制約がない場合
# どんなに入れ替えても、同じ行・同じ列に存在する数の組み合わせは変わらない
# つまり入れ替え可能な列のペアは限られている
# 行・列に対して、入れ替え可能なペアの通り数を算出し、それを掛け合わせたものが答え

# 行Aと行Bが入れ替え可能で行Bと行Cが入れ替え可能な場合、
# 行Aと行Cが直接入れ替え可能でなくても操作によって行A,行Cは入れ替えることが出来る
# UnionFindで入れ替え可能なもの同士をグルーピングする

# by size
# 0-indexed

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:
      #print("行",i,j,"入れ替え可能")
      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)
  # sizの階乗通り分組み合わせが存在する
  #print("root",root,"siz",siz)
  pat = 1
  for p in range(1, siz + 1):
    pat *= p
    pat %= DIV
  row_pat *= pat
  row_pat %= DIV
  
#print("row_pat",row_pat)

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:
      #print("列",i,j,"入れ替え可能")
      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)
  #print("root",root,"siz",siz)
  pat = 1
  for p in range(1, siz + 1):
    pat *= p
    pat %= DIV
  col_pat *= pat
  col_pat %= DIV
  
#print("col_pat",col_pat)
  
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:
    # 親管理リストと高さ管理リストを初期化し、
    # 要素N個のUnion-Find森を作成する。
    # 親管理リストは、基本的には自分のひとつ上の親を表すが、
    # 値が負の場合には、自身が最上位の親(リーダー)であることを表し、
    # 自分を含めたグループの人数を管理することとする
    def __init__(self, N):
        self.parent = [-1] * N
        self.rank = [0] * N
        self.group_count = N
        self.N = N
    
    # xの所属するグループのリーダーを返す
    def find(self, x):
        # 自分自身がリーダーなら、自分を返す
        if self.parent[x] < 0:
            return x

        # 再帰的に捜索し、見つかれば繋ぎ変えておく
        # (計算量が増える＝面倒くさいので)高さ管理は行わない
        par = self.find(self.parent[x])
        self.parent[x] = par
        return par

    # xとyのグループを統合する
    def unite(self, x, y):
        # それぞれのリーダーに対する操作を行うことになる
        x = self.find(x)
        y = self.find(y)

        # リーダーが同じなら何もする必要がない
        if x == y:
            return

        # 木の高さが同じ場合：
        # グループの人数を合計しつつ適当に繋ぎ、繋げられた方の高さを1増やす
        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
        
        # 統合された場合、グループ数は1減る
        self.group_count -= 1
    
    # xとyが同じグループかどうかを調べる
    def samep(self, x, y):
        return self.find(x) == self.find(y)

    # xの所属するグループのメンバーをリストで返す
    def get_group_member_list(self, x):
        x = self.find(x)
        return [i for i in range(self.N) if self.find(i) == x]
    
    # 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 LF(): return [float(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()

# Union-Find -- START --
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]
# Union-Find --- END ---

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):
        # print(field[i][k],field[j][k])
        if field[i][k]+field[j][k]>K:
          l1[j]=False
          break
    # print(l1[j])
    for j in range(i+1,n):
      if l1[j]:
        uf1.unite(i,j)

    # for i in range(n):
    #   print(uf1.find(i))

  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)

  # for i in range(n):
  #   print(uf2.find(i))

  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

  # print(ans1)

  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

# main()
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
# defaultdict(int)
input=lambda: sys.stdin.readline().strip()
sys.setrecursionlimit(11451419)
from decimal import ROUND_HALF_UP,Decimal  #変換後の末尾桁を0や0.01で指定
  #Decimal((str(0.5)).quantize(Decimal('0'), rounding=ROUND_HALF_UP))
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  #frac(a,b)で正確なa/b
# @lru_cache(maxsize=10**10)
#######ここまでテンプレ#######
#ソート、"a"+"b"、再帰ならPython3の方がいい
#######ここから天ぷら########

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
# print(kai)

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())

# uf=UnionFind(n)
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)

#Union Find Tree
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 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]

def union(x, y, uf, rank):
  px, py = find(x, uf), find(y, uf)
  if px != py:
    if rank[px] > rank[py]:
      px, py = py, px
    rank[py] += rank[px]
    uf[px] = py
  
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])):
      union(i, j, uf1, rank1)
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])):
      union(i, j, uf2, rank2)
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 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]

def union(x, y, uf, rank):
    px, py = find(x, uf), find(y, uf)
    if px != py:
        if rank[px] > rank[py]:
            px, py = py, px
        rank[py] += rank[px]
        uf[px] = py
  
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 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])
#print(rowLis)
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])
#print(colLis)
parList = [i for i in range(n+1)]
q = co.deque()
colDic = UnionFind(n,len(colLis),colLis)

ans = 1
#print(rowDic)
#print(colDic)
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)

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): # sizeをO(1)で引けるversion
    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): # 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,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): # x,yが連結かどうか
        return self.find(x) == self.find(y)
    def size(self,x): # xの属する連結sizeをO(1)で出す
        x = self.find(x)
        return self.sizebox[x]
    def allfind(self): # findを1周する
        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)

for i in range(n):
    ufr.find(i)
    ufc.find(i)

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)

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]


# i-index
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
        else:
            if 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):
            #print(i, self.find(i))
            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)]
#print(A)


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])
    #print(edge)
    
    for a, b in edge:
        uf.union(a, b)
    #print("check")
    #uf.check()
    
    ret = 1
    sl = uf.size_list(N)
    #print(sl)
    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
#print(A)

uf = UnionFind(N)
answer = (answer * f(A)) % MOD
print(answer)