#include <bits/stdc++.h>
#define ALL(A) (A).begin(), (A).end()
#define ll long long
#define rep(i, n) for (int i = 0; i < (n); i++)

using namespace std;
template<class T> inline bool chmax(T& a, T b) { if (a < b) { a = b; return 1; } return 0; }
template<class T> inline bool chmin(T& a, T b) { if (a > b) { a = b; return 1; } return 0; }

int dx[] = { 0, 1, -1, 0, 1, -1, 1, -1 };  // i<4:4way i<8:8way
int dy[] = { 1, 0, 0, -1, 1, -1, -1, 1 };

const ll mod = 998244353;
const ll INF = -1 * ((1LL << 63) + 1);
const int inf = -1 * ((1 << 31) + 1);
struct UnionFind {
  vector< int > data;
 
  UnionFind(int sz) {
    data.assign(sz, -1);
  }
 
  bool unite(int x, int y) {
    x = find(x), y = find(y);
    if(x == y) return (false);
    if(data[x] > data[y]) swap(x, y);
    data[x] += data[y];
    data[y] = x;
    return (true);
  }
 
  int find(int k) {
    if(data[k] < 0) return (k);
    return (data[k] = find(data[k]));
  }
 
  bool findSet(int x,int y){
    return find(x) == find(y);
  }
 
  int size(int k) {
    return (-data[find(k)]);
  }
};

ll f(ll x){
    ll ret = 1;
    for(int i=0;i<x;i++){
        ret *= i+1;
        ret %= mod;
    }
    return ret % mod;
}

int main(void){
    cin.tie(0);
    ios::sync_with_stdio(false);
    cout << fixed << setprecision(20);
    int n,m;
    cin >> n >> m;
    vector<vector<int>> a(n,vector<int>(n));
    rep(i,n)rep(j,n)cin >> a[i][j];
    ll ans = 1;
    // まずは行についてスワップできる組み合わせを見つけていく
    UnionFind unitate(n),uniyoko(n);
    set<int> tate,yoko;
    for(int i=0;i<n;i++){
        for(int j=i+1;j<n;j++){
            bool ok = true;
            for(int k=0;k<n;k++){
                if(a[i][k] + a[j][k] > m)ok = false;
            }
            if(ok){
                unitate.unite(i,j);
            }
        }
    }
    for(int i=0;i<n;i++){
        int size = unitate.size(i);
        int now = unitate.find(i);
        if(tate.count(now))continue;
        else{
            tate.insert(now);
            ans *= f(size);
            ans %= mod;
        }
    }

    // 次は列についてスワップできる組み合わせを見つける
    for(int i=0;i<n;i++){
        for(int j=i+1;j<n;j++){
            bool ok = true;
            for(int k=0;k<n;k++){
                if(a[k][i] + a[k][j] > m)ok = false;
            }
            if(ok){
                uniyoko.unite(i,j);
            }
        }
    }
    for(int i=0;i<n;i++){
        int size = uniyoko.size(i);
        int now = uniyoko.find(i);
        if(yoko.count(now))continue;
        else{
            yoko.insert(now);
            ans *= f(size);
            ans %= mod;
        }
    }
    cout << ans << endl;
}
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
#include<vector>
using namespace std;
const int M=1e6+5;
const int N=1e3+5;
#define int long long
int Max(int x,int y){return x>y?x:y;}
int Min(int x,int y){return x>y?y:x;}
int Abs(int x){return x>0?x:-x;}
int read(){
	int k=1,num=0;
	char s=getchar();
	while(s>'9'||s<'0'){
		if(s=='-') k=-1;
		s=getchar();
	}
	while(s>='0'&&s<='9'){
		num=(num<<1)+(num<<3)+(s-'0');
		s=getchar();
	}
	return num*k;
} 
int cnt[M];
signed main(){
	int L=read(),R=read();
	for(int i=2;i<=R;i++){
		if(cnt[i]==0){
			for(int j=i;j<=R;j+=i) cnt[j]++;
			for(int j=i*i;j<=R;j+=i*i) cnt[j]=-(1ll<<60);
		}
	}
	int ans=0;
	for(int k=2;k<=R;k++){
		if(cnt[k]<0) continue;
		int val=R/k-(L-1)/k;
		val=val*(val-1)/2;
		if(cnt[k]%2) ans+=val;
		else ans-=val;
	}
	for(int i=Max(2ll,L);i<=R;i++){
		ans-=R/i-1;
	}
	printf("%lld",ans*2);
	return 0;
}