// https://atcoder.jp/contests/ttpc2022/submissions/35187581

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const long long MOD = 998244353ll;
const long long INF = 2305843009213693951ll;

bool comp(pair<ll,ll>& a,pair<ll,ll>& b){
    if(a.first*b.second < b.first*a.second){
        return true;
    }
    return false;
}

pair<long long,long long> reduct(pair<long long,long long> x){
    long long g = gcd(x.first,x.second);
    x.first/=g;x.second/=g;
    if(x.second<0){
        x.first*=-1;
        x.second*=-1;
    }
    return x;
}

int main(void){
    int n, m;
    cin >> n >> m;
    int stats[31][31][31]{};
    for (int i = 0; i < m; i++) {
      ll a, b, c, d;
      cin >> a >> b >> c >> d;
      for (int x = -15; x <= 15; x++) for (int y = -15; y <= 15; y++) for (int z = -15; z <= 15; z++) {
        ll v = a * x + b * y + c * z + d;
        int& s = stats[x+15][y+15][z+15];
        if (v == 0) s++;
        else if (v < 0) s = -1e9;
      }
    }
    
    // ll N,K;
    // cin >> N >> K;
    vector<array<ll,3>> P;
    // for(int i=0;i<N;i++){
        // cin >> P[i][0] >> P[i][1] >> P[i][2];
    // }
    ll K = n;
    for (int x = -15; x <= 15; x++) for (int y = -15; y <= 15; y++) for (int z = -15; z <= 15; z++) {
      if (stats[x+15][y+15][z+15] >= 2) P.push_back({x, y, z});
    }
    ll N = P.size();

    //まず内部が空でない3-単体を探す
    array<ll,4> In = {0,1,-1,-1};
    for(int i=2;i<N;i++){
        array<ll,3> d1 = {P[1][0]-P[0][0],P[1][1]-P[0][1],P[1][2]-P[0][2]};
        array<ll,3> d2 = {P[i][0]-P[0][0],P[i][1]-P[0][1],P[i][2]-P[0][2]};
        array<ll,3> n = {d1[1]*d2[2]-d1[2]*d2[1],d1[2]*d2[0]-d1[0]*d2[2],d1[0]*d2[1]-d1[1]*d2[0]};
        if(n[0]!=0 || n[1]!=0 || n[2]!=0){
            In[2] = i;
            break;
        }
    }
    for(int i=2;i<N;i++){
        if(i==In[2])continue;
        array<ll,3> d1 = {P[1][0]-P[0][0],P[1][1]-P[0][1],P[1][2]-P[0][2]};
        array<ll,3> d2 = {P[In[2]][0]-P[0][0],P[In[2]][1]-P[0][1],P[In[2]][2]-P[0][2]};
        array<ll,3> d3 = {P[i][0]-P[0][0],P[i][1]-P[0][1],P[i][2]-P[0][2]};
        array<ll,3> n = {d1[1]*d2[2]-d1[2]*d2[1],d1[2]*d2[0]-d1[0]*d2[2],d1[0]*d2[1]-d1[1]*d2[0]};
        ll d = d3[0]*n[0] + d3[1]*n[1] + d3[2]*n[2];
        
        if(d!=0){
            In[3] = i;
            break;
        }
    }

    for(int i=0;i<4;i++){
        swap(P[i],P[In[i]]);
    }

    //同一平面上にない4点をとれた
    ll M = 4;
    vector<array<ll,3>> F = {
        {0,1,2},
        {0,1,3},
        {0,2,3},
        {1,2,3}
    };
    
    vector<array<ll,3>> Nor(M);

    //法線ベクトルを求める
    for(int i=0;i<M;i++){
        array<ll,3> d1 = {P[F[i][1]][0]-P[F[i][0]][0],P[F[i][1]][1]-P[F[i][0]][1],P[F[i][1]][2]-P[F[i][0]][2]};
        array<ll,3> d2 = {P[F[i][2]][0]-P[F[i][0]][0],P[F[i][2]][1]-P[F[i][0]][1],P[F[i][2]][2]-P[F[i][0]][2]};
        array<ll,3> n = {d1[1]*d2[2]-d1[2]*d2[1],d1[2]*d2[0]-d1[0]*d2[2],d1[0]*d2[1]-d1[1]*d2[0]};
        Nor[i] = n;
    }

    //法線ベクトルを外向きに統一する
    ll avex=0,avey=0,avez=0;
    for(int i=0;i<4;i++){
        avex += P[i][0];
        avey += P[i][1];
        avez += P[i][2];
    }

    for(int i=0;i<M;i++){
        ll x = 4*P[F[i][0]][0] - avex,y = 4*P[F[i][0]][1] - avey,z = 4*P[F[i][0]][2] - avez;
        ll p = Nor[i][0]*x + Nor[i][1]*y + Nor[i][2]*z;

        if(p<0){
            swap(F[i][1],F[i][2]);
            Nor[i][0]*=-1;Nor[i][1]*=-1;Nor[i][2]*=-1;
        }
    }
    
    //残りのN-4点を凸包につける
    //horizon: i-j辺が地平線にあるかどうか
    int horizon[200][200] = {};

    for(int i=4;i<N;i++){
        // 法線と(面の点の一つ-追加する点)の内積を求める
        // すべて正なら内部にある(零なら同じ平面にある)
        // 負があるならばその面を削除する

        //R:内積
        vector<ll> R(M);
        for(int j=0;j<M;j++){
            array<ll,3> d = {P[F[j][0]][0]-P[i][0],P[F[j][0]][1]-P[i][1],P[F[j][0]][2]-P[i][2]};
            R[j] = Nor[j][0]*d[0] + Nor[j][1]*d[1] + Nor[j][2]*d[2];
            if(R[j]<0){
                horizon[min(F[j][0],F[j][1])][max(F[j][0],F[j][1])] ^= 1;
                horizon[min(F[j][0],F[j][2])][max(F[j][0],F[j][2])] ^= 1;
                horizon[min(F[j][1],F[j][2])][max(F[j][1],F[j][2])] ^= 1;
            }
        }

        vector<array<ll,3>> Ft,Nort;
        
        for(int j=0;j<M;j++){
            if(R[j]<0){
                for(int k=0;k<3;k++){
                    ll u,v;
                    if(k==0){
                        u = F[j][0],v = F[j][1];
                    }else if(k==1){
                        u = F[j][0],v = F[j][2];
                    }else{
                        u = F[j][1],v = F[j][2];
                    }

                    if(horizon[min(u,v)][max(u,v)]!=0){
                        horizon[min(u,v)][max(u,v)] = 0;

                        ll d1 = i, d2 = u, d3 = v;
                        array<ll,3> du = {P[d2][0]-P[d1][0],P[d2][1]-P[d1][1],P[d2][2]-P[d1][2]},dv = {P[d3][0]-P[d1][0],P[d3][1]-P[d1][1],P[d3][2]-P[d1][2]};
                        array<ll,3> d = {du[1]*dv[2]-du[2]*dv[1],du[2]*dv[0]-du[0]*dv[2],du[0]*dv[1]-du[1]*dv[0]};
                        ll p = d[0]*(4*P[d1][0]-avex) + d[1]*(4*P[d1][1]-avey) + d[2]*(4*P[d1][2]-avez);
                        if(p<0){
                            swap(d2,d3);
                            d[0]*=-1;d[1]*=-1;d[2]*=-1;
                        }

                        Ft.push_back({d1,d2,d3});
                        Nort.push_back(d);
                    }
                }
            }
        }

        ll now = 0;
        for(int j=0;j<M;j++){
            if(R[j]>=0){
                F[now] = F[j];
                Nor[now] = Nor[j];
                now++;
            }
        }

        M = now + Ft.size();
        F.resize(M);
        Nor.resize(M);
        for(int j=0;j<(int)Ft.size();j++){
            F[now+j] = Ft[j];
            Nor[now+j] = Nort[j];
        }
    }

    //凸包の体積の8倍
    ll Vol = 0;
    for(int i=0;i<M;i++){
        array<ll,3> d1 = P[F[i][0]],d2 = P[F[i][1]],d3 = P[F[i][2]];
        Vol += d1[0]*d2[1]*d3[2] + d1[1]*d2[2]*d3[0] + d1[2]*d2[0]*d3[1]
             - d1[2]*d2[1]*d3[0] - d1[0]*d2[2]*d3[1] - d1[1]*d2[0]*d3[2];
    }

    //凸包が求まったので、その格子点の個数を求める
    //境界つき内部と境界なし内部が欲しいので、L(内部)とB(境界)を求める
    ll L = 0,B = 0;
    ll Xmin = INF,Xmax = -INF,Ymin = INF,Ymax = -INF,Zmin = INF,Zmax = -INF;
    for(int i=0;i<N;i++){
        Xmin = min(Xmin,P[i][0]);
        Xmax = max(Xmax,P[i][0]);
        Ymin = min(Ymin,P[i][1]);
        Ymax = max(Ymax,P[i][1]);
        Zmin = min(Zmin,P[i][2]);
        Zmax = max(Zmax,P[i][2]);
    }

    //x,y座標を決め打ってzを求める
    //各面について、平面がz軸を含むか含まないかで場合分け
    //もしz軸を含むなら面と決め打ったx,yの共通部分は空または線分 その両端を求める
    //もしz軸を含まないなら共通部分は空または点 上か下かを調べる

    for(ll x=Xmin;x<=Xmax;x++){
        for(ll y=Ymin;y<=Ymax;y++){
            pair<ll,ll> H={-INT_MAX,1},D={INT_MAX,1};
            bool boundary = false;

            for(int i=0;i<M;i++){
                ll a = P[F[i][1]][0]-P[F[i][0]][0], b = P[F[i][1]][1]-P[F[i][0]][1];
                ll c = P[F[i][2]][0]-P[F[i][0]][0], d = P[F[i][2]][1]-P[F[i][0]][1];
                
                if(a*d == b*c){
                    //z軸を含む
                    vector<pair<ll,ll>> W = {
                        {P[F[i][0]][0],P[F[i][0]][1]},
                        {P[F[i][1]][0],P[F[i][1]][1]},
                        {P[F[i][2]][0],P[F[i][2]][1]}
                    };
                    sort(W.begin(),W.end());
                    
                    pair<ll,ll> v1 = {W[2].first-W[0].first,W[2].second-W[0].second};
                    pair<ll,ll> v2 = {x-W[0].first,y-W[0].second};
                    if(v1.first*v2.second-v1.second*v2.first!=0)continue;
                    if(v1.first*v2.first + v1.second*v2.second<0)continue;
                    if(v1.first*v1.first+v1.second*v1.second < v2.first*v2.first+v2.second*v2.second)continue;
                    boundary = true;

                    vector<pair<ll,ll>> E;
                    for(int j=0;j<3;j++){
                        array<ll,3> u = P[F[i][j]], v = P[F[i][(j+1)%3]];
                        ll s1 = x-u[0],s2 = v[0]-x;
                        if(s1==0 && s2==0){s1 = y-u[1];s2 = v[1]-y;}
                        if(s1<0 && s2<0){s1*=-1;s2*=-1;}
                        if(s1<0 || s2<0)continue;

                        if(s1+s2==0){
                            //線分がz軸に平行
                            if(H.first*1 < u[2]*H.second)H = {u[2],1};
                            if(D.first*1 > u[2]*D.second)D = {u[2],1};
                            if(H.first*1 < v[2]*H.second)H = {v[2],1};
                            if(D.first*1 > v[2]*D.second)D = {v[2],1};

                        }else{
                            pair<ll,ll> s = reduct({s2*u[2]+s1*v[2],s1+s2});
                            
                            //upper point
                            if(H.first*s.second < s.first*H.second)H = s;

                            //lower point
                            if(D.first*s.second > s.first*D.second)D = s;
                        }
                    }
                }else{
                    //z軸を含まない
                    
                    //(B-A)s+(C-A)t=((x,y,z)-A) を満たすs,t 実際の値はこれをad-bcで割ったもの
                    ll s = d*(x-P[F[i][0]][0])-c*(y-P[F[i][0]][1]);
                    ll t = -b*(x-P[F[i][0]][0])+a*(y-P[F[i][0]][1]);
                    ll det = a*d-b*c;
                    if(det<0){
                        s*=-1;t*=-1;det*=-1;
                    }

                    if(s<0 || t<0 || s+t>det)continue;
                    pair<ll,ll> z = reduct({P[F[i][0]][2]*(det-s-t)+P[F[i][1]][2]*s+P[F[i][2]][2]*t,det});

                    //upper point
                    if(H.first*z.second < z.first*H.second)H = z;

                    //lower point
                    if(D.first*z.second > z.first*D.second)D = z;
                }
            }

            if(H != pair<ll,ll>{-INT_MAX,1} && D !=pair<ll,ll>{INT_MAX,1}){
                ll Hz,Dz;

                if(H.first>=0){
                    Hz = H.first/H.second;
                }else{
                    Hz = -((-H.first+H.second-1)/H.second);
                }

                if(D.first>=0){
                    Dz = (D.first+D.second-1)/D.second;
                }else{
                    Dz = -((-D.first)/D.second);
                }

                L += Hz-Dz+1;

                H = reduct(H),D = reduct(D);

                if(boundary){
                    B += Hz-Dz+1;
                }else{
                    if(H.second == 1)B++;
                    if(D.second == 1 && H!=D)B++;
                }

            }
        }
    }

    Vol = (Vol*166374059)%MOD;
    L %= MOD;
    B %= MOD;

    //cout << Vol  << " " << L << " " << B << endl;

    /*
    (L_P( 1) - V)   (1  1 1)(c_2)
    (L_P( 0)    ) = (0  0 1)(c_1)
    (L_P(-1) + V)   (1 -1 1)(  1)
    */

    /*
    (c_2)       (1 -2  1)( L - Vol)
    (c_1) = 1/2 (1  0 -1)(    1   )
    (  1)       (0  2  0)(-L+B+Vol)
    */

    //これで内部・内部と境界の格子点の個数と体積が求まったのでエルハート多項式を求める
    //逆行列を埋め込む
    ll a = (L-Vol+MOD)%MOD,b = (-L+B+Vol+MOD)%MOD;
    vector<ll> C(4);
    C[0] = (0*a +       2*1 +       0*b)%MOD;
    C[1] = (1*a +       0*1 + (MOD-1)*b)%MOD;
    C[2] = (1*a + (MOD-2)*1 +       1*b)%MOD;
    C[3] = (2*Vol)%MOD;
    for(int i=0;i<4;i++){
        C[i] = (C[i]*499122177)%MOD;//2で割る
    }

    //エルハート多項式が求まったのでKを代入してK th dilateのdiscrete volumeを求める
    K %= MOD;
    if (K) K = MOD - K;
    ll V = 0;
    for(int i=0;i<4;i++){
        ll num = C[i];
        for(int j=0;j<i;j++){
            num = (num*K)%MOD;
        }
        V = (V + num)%MOD;
    }
    if (V) V = MOD - V;
    cout << V << endl;

    return 0;
}