A subarray of array a from index l to the index r is the array [a_l, a_{l+1}, ..., a_{r}]. The number of occurrences of the array b in the array a is the number of subarrays of a such that they are equal to b.

You are given n arrays A_1, A_2, ..., A_n; the elements of these arrays are integers from 1 to k. You have to build an array a consisting of m integers from 1 to k in such a way that, for every given subarray A_i, the number of occurrences of A_i in the array a is not less than the number of occurrences of each non-empty subarray of A_i in a. Note that if A_i doesn't occur in a, and no subarray of A_i occurs in a, this condition is still met for A_i.

Your task is to calculate the number of different arrays a you can build, and print it modulo 998244353.

Input

The first line contains three integers n, m and k (1 ≤ n, m, k ≤ 3 ⋅ 10^5) — the number of the given arrays, the desired length of the array a, and the upper bound on the values in the arrays.

Then n lines follow. The i-th line represents the array A_i. The first integer in the i-th line is c_i (1 ≤ c_i ≤ m) — the number of elements in A_i; then, c_i integers from 1 to k follow — the elements of the array A_i.

Additional constraint on the input: ∑_{i=1}^n c_i ≤ 3 ⋅ 10^5; i. e., the number of elements in the given arrays in total does not exceed 3 ⋅ 10^5.

Output

Print one integer — the number of different arrays a you can build, taken modulo 998244353.

Examples

Input


2 4 3
2 1 2
1 3


Output


5


Input


2 4 3
2 1 2
3 3 2 1


Output


0


Input


1 42 1337
2 13 31


Output


721234447
#include <bits/stdc++.h>
using namespace std;
const double EPS = 1e-12;
const int INF = 1e9 + 10;
template <typename T>
using MinPQ = priority_queue<T, vector<T>, greater<T>>;
namespace SA {
vector<int> sa;
vector<int> rank;
vector<int> lcp;
namespace {
vector<int> cnt, cur;
}
inline void PushS(vector<int>::iterator s, int x) { sa[cur[s[x]]--] = x; }
inline void PushL(vector<int>::iterator s, int x) { sa[cur[s[x]]++] = x; }
void InducedSort(vector<int>::iterator s, vector<int>::iterator t, int n,
                 vector<int>::iterator v, int nv, int m) {
  fill_n(sa.begin(), n, -1);
  fill_n(cnt.begin(), m, 0);
  for (int i = 0; i < n; i++) cnt[s[i]]++;
  for (int i = 1; i < m; i++) cnt[i] += cnt[i - 1];
  for (int i = 0; i < m; i++) cur[i] = cnt[i] - 1;
  for (int i = nv - 1; i >= 0; i--) PushS(s, v[i]);
  for (int i = 1; i < m; i++) cur[i] = cnt[i - 1];
  for (int i = 0; i < n; i++)
    if (sa[i] > 0 && t[sa[i] - 1]) PushL(s, sa[i] - 1);
  for (int i = 0; i < m; i++) cur[i] = cnt[i] - 1;
  for (int i = n - 1; i >= 0; i--)
    if (sa[i] > 0 && !t[sa[i] - 1]) PushS(s, sa[i] - 1);
}
void SaIs(int n, int m, vector<int>::iterator s, vector<int>::iterator t,
          vector<int>::iterator p) {
  t[n - 1] = 0;
  for (int i = n - 2; i >= 0; i--)
    t[i] = (s[i] == s[i + 1]) ? t[i + 1] : s[i] > s[i + 1];
  int np = 0;
  rank[0] = -1;
  for (int i = 1; i < n; i++)
    if (!t[i] && t[i - 1]) {
      p[np] = i;
      rank[i] = np++;
    } else
      rank[i] = -1;
  InducedSort(s, t, n, p, np, m);
  vector<int>::iterator s1 = s + n;
  int ch = -1;
  for (int i = 0, x, y; i < n; i++)
    if (~(x = rank[sa[i]])) {
      if (ch < 1 || p[x + 1] - p[x] != p[y + 1] - p[y])
        ch++;
      else {
        for (int j = p[x], k = p[y]; j <= p[x + 1]; j++, k++)
          if ((s[j] << 1 | t[j]) != (s[k] << 1 | t[k])) {
            ch++;
            break;
          }
      }
      s1[y = x] = ch;
    }
  if (ch + 1 < np)
    SaIs(np, ch + 1, s1, t + n, p + np);
  else {
    for (int i = 0; i < np; i++) sa[s1[i]] = i;
  }
  for (int i = 0; i < np; i++) s1[i] = p[sa[i]];
  InducedSort(s, t, n, s1, np, m);
}
template <typename T>
vector<int> GetLcp(const T* s, int n, const vector<int>& sa,
                   const vector<int>& rank) {
  int len = 0;
  vector<int> lcp(n);
  lcp[0] = 0;
  for (int i = 0; i < n; ++i) {
    if (rank[i] == n - 1) {
      len = 0;
      continue;
    }
    int nxt = sa[rank[i] + 1];
    while (i + len < n && nxt + len < n && s[i + len] == s[nxt + len]) ++len;
    lcp[rank[i] + 1] = len;
    if (len > 0) --len;
  }
  return lcp;
}
namespace {
vector<int> s, p;
vector<int> t;
}  // namespace
template <typename T>
void GetSa(const T* s0, int n) {
  int m = *max_element(s0, s0 + n) + 2;
  rank.resize(max(m, n + 1));
  fill_n(rank.begin(), m, 0);
  rank[0] = 1;
  for (int i = 0; i < n; i++) rank[s0[i] + 1] = 1;
  for (int i = 1; i < m; i++) rank[i] += rank[i - 1];
  s.resize((n + 1) << 1);
  for (int i = 0; i < n; i++) s[i] = rank[s0[i] + 1] - 1;
  s[n++] = 0;
  m = rank[m - 1];
  t.resize(int((s).size()));
  p.resize(n);
  sa.resize(n);
  cur.resize(n);
  cnt.resize(n);
  SaIs(n, m, s.begin(), t.begin(), p.begin());
  assert(sa[0] == n - 1);
  for (int i = 0; i + 1 < n; ++i) {
    sa[i] = sa[i + 1];
    rank[sa[i]] = i;
  }
  sa.resize(n - 1);
  rank.resize(n - 1);
  lcp = GetLcp(s0, n - 1, sa, rank);
}
template <typename Seq>
void GetSa(const Seq& s0) {
  GetSa(s0.data(), int((s0).size()));
}
};  // namespace SA
template <class T>
struct Rmq {
  int n;
  vector<T> a;
  vector<vector<int>> f;
  vector<int> f_idx;
  Rmq(const vector<T>& a = {}) { Reset(a); }
  void Reset(const vector<T>& a) {
    this->a = a;
    n = int((a).size());
    vector<int> cur(n);
    iota((cur).begin(), (cur).end(), 0);
    f.clear();
    f.push_back(move(cur));
    if (n == 0) return;
    for (int p = 1;; ++p) {
      int len = 1 << p;
      int len2 = len >> 1;
      cur.resize(n - len + 1);
      for (int i = 0; i < int((cur).size()); ++i) {
        int u1 = f[p - 1][i], u2 = f[p - 1][i + len - len2];
        if (a[u1] < a[u2])
          cur[i] = u1;
        else
          cur[i] = u2;
      }
      f.push_back(move(cur));
      if ((len << 1) >= n) break;
    }
    f_idx.resize(n + 1);
    f_idx[0] = 0;
    for (int i = 1; i <= n; ++i) {
      int p = f_idx[i >> 1];
      while (1 << (p + 1) < i) ++p;
      f_idx[i] = p;
    }
  }
  int QueryIndex(int l, int r) {
    if (l > r) swap(l, r);
    int len = r - l + 1;
    int p = f_idx[len];
    int u1 = f[p][l], u2 = f[p][r - (1 << p) + 1];
    if (a[u1] < a[u2]) return u1;
    return u2;
  }
};
long long GcdExt(long long a, long long b, long long& x, long long& y) {
  if (b == 0) {
    x = 1;
    y = 0;
    return a;
  }
  long long d = GcdExt(b, a % b, x, y);
  long long tmp = y;
  y = x - y * (a / b);
  x = tmp;
  return d;
}
struct ModInt {
  static constexpr int MOD = 998244353;
  int v = 0;
  ModInt() {}
  ModInt(long long x) {
    v = x % MOD;
    if (v < 0) v += MOD;
  }
  friend inline bool operator==(const ModInt& l, const ModInt& r) {
    return l.v == r.v;
  }
  friend inline bool operator!=(const ModInt& l, const ModInt& r) {
    return !(l == r);
  }
  ModInt& operator+=(const ModInt& r) {
    v += r.v;
    if (v >= MOD) v -= MOD;
    return *this;
  }
  ModInt& operator-=(const ModInt& r) {
    v -= r.v;
    if (v < 0) v += MOD;
    return *this;
  }
  ModInt& operator*=(const ModInt& r) {
    v = ((long long)v * r.v) % MOD;
    return *this;
  }
  ModInt& operator/=(const ModInt& r) {
    *this *= r.Inv();
    return *this;
  }
  friend ModInt operator+(ModInt l, const ModInt& r) {
    l += r;
    return l;
  }
  friend ModInt operator-(ModInt l, const ModInt& r) {
    l -= r;
    return l;
  }
  friend ModInt operator*(ModInt l, const ModInt& r) {
    l *= r;
    return l;
  }
  friend ModInt operator/(ModInt l, const ModInt& r) {
    l /= r;
    return l;
  }
  ModInt Inv() const {
    long long x, y;
    assert(GcdExt(v, MOD, x, y) == 1);
    return x;
  }
  ModInt Pow(long long n) {
    if (!n) return 1;
    ModInt r = Pow(n >> 1);
    r *= r;
    if (n & 1) r *= v;
    return r;
  }
};
struct DisjointSet {
  vector<int> f;
  DisjointSet(int n = 0) { Reset(n); }
  void Reset(int n) {
    f.resize(n);
    iota(f.begin(), f.end(), 0);
  }
  int Find(int u) {
    if (f[u] == u) return u;
    return f[u] = Find(f[u]);
  }
  bool Merge(int s, int t) {
    s = Find(s);
    t = Find(t);
    if (s != t) {
      f[s] = t;
      return 1;
    }
    return 0;
  }
  void Add(int u) {
    int nf = int((f).size());
    if (u < nf) return;
    f.resize(u + 1);
    iota(f.begin() + nf, f.end(), nf);
  }
};
template <class T>
struct DirectedWeightedGraph {
  constexpr static T MAXW = numeric_limits<T>::max();
  int n;
  vector<vector<pair<int, T>>> adj;
  DirectedWeightedGraph(int n = 0) { Reset(n); }
  void Reset(int n) {
    this->n = n;
    adj.resize(n);
    for (auto& i : (adj)) i.clear();
  }
  void AddEdge(int u, int v, T w) { adj[u].emplace_back(v, w); }
  vector<int> TarjanScc(vector<int>& comp_id) {
    comp_id.assign(n, -1);
    vector<int> comp_size;
    int dfsv = 0;
    vector<int> dfs(n, -1);
    vector<int> low(n);
    vector<int> st;
    vector<bool> in_st(n, 0);
    const std::function<void(int)> Dfs = [&](int u) {
      dfs[u] = dfsv++;
      low[u] = dfs[u];
      st.push_back(u);
      in_st[u] = 1;
      for (auto& e : (adj[u])) {
        int v = e.first;
        if (dfs[v] < 0) {
          Dfs(v);
          low[u] = min(low[u], low[v]);
        } else if (in_st[v]) {
          low[u] = min(low[u], dfs[v]);
        }
      }
      if (low[u] == dfs[u]) {
        int c = 0;
        while (1) {
          int v = st.back();
          st.pop_back();
          in_st[v] = 0;
          comp_id[v] = int((comp_size).size());
          ++c;
          if (v == u) break;
        }
        comp_size.push_back(c);
      }
    };
    for (int i = 0; i < n; i++)
      if (dfs[i] < 0) {
        Dfs(i);
        assert(st.empty());
      }
    return comp_size;
  }
  vector<int> TopologicalSort() {
    vector<char> vis(n, 0);
    vector<int> ans;
    ans.reserve(n);
    const std::function<void(int)> Dfs = [&](int u) {
      vis[u] = 1;
      for (auto& e : (adj[u])) {
        int v = e.first;
        if (!vis[v]) Dfs(v);
      }
      ans.push_back(u);
    };
    for (int i = 0; i < n; ++i)
      if (!vis[i]) Dfs(i);
    reverse((ans).begin(), (ans).end());
    return ans;
  }
};
int n, m, k;
vector<vector<int>> a;
vector<int> all;
vector<int> apos;
void Solve() {
  scanf("%d%d%d", &n, &m, &k);
  a.resize(n);
  for (int i = 0; i < n; ++i) {
    int c;
    scanf("%d", &c);
    a[i].resize(c);
    for (auto& x : (a[i])) scanf("%d", &x);
  }
  sort((a).begin(), (a).end());
  a.resize(unique((a).begin(), (a).end()) - a.begin());
  n = int((a).size());
  all.clear();
  all.reserve(300000 + n);
  apos.resize(n);
  for (int i = 0; i < n; ++i) {
    apos[i] = int((all).size());
    all.insert(all.end(), (a[i]).begin(), (a[i]).end());
    all.push_back(k + 1 + i);
  }
  SA::GetSa(all);
  if (0) {
    for (int i = 0; i < int((all).size()); ++i) {
      42;
    }
  }
  Rmq<int> lcp_rmq(SA::lcp);
  vector<char> is_substr(n, 0);
  for (int i = 0; i < n; ++i) {
    int r = SA::rank[apos[i]];
    int len = int((a[i]).size());
    if (SA::lcp[r] >= len ||
        (r + 1 < int((all).size()) && SA::lcp[r + 1] >= len)) {
      is_substr[i] = 1;
    }
    42;
  }
  DisjointSet ds(k + 1);
  vector<char> good_ch(k + 1, 1);
  vector<char> good_word(m, 1);
  {
    for (int i = 0; i < n; ++i) {
      for (auto& x : (a[i])) {
        ds.Merge(a[i][0], x);
      }
    }
    vector<int> cnt_ch(k + 1, 0);
    vector<int> cnt_ch_same_word(k + 1, 0);
    for (int i = 0; i < n; ++i) {
      if (is_substr[i]) continue;
      for (auto& x : (a[i])) {
        cnt_ch[x]++;
        cnt_ch_same_word[x]++;
      }
      bool good = 1;
      for (auto& x : (a[i])) {
        if (cnt_ch_same_word[x] > 1) good = 0;
        cnt_ch_same_word[x] = 0;
      }
      if (!good) good_ch[ds.Find(a[i][0])] = 0;
    }
    for (int i = 1; i <= k; ++i)
      if (cnt_ch[i] > 2) {
        good_ch[ds.Find(i)] = 0;
      }
    for (int i = 1; i <= k; ++i) good_ch[i] = good_ch[ds.Find(i)];
    for (int i = 0; i < n; ++i) {
      good_word[i] = !is_substr[i] && good_ch[ds.Find(a[i][0])];
      42;
    }
  }
  DirectedWeightedGraph<int> g(n);
  {
    vector<vector<int>> ch_to_word(k + 1);
    vector<unordered_map<int, int>> ch_pos(m);
    for (int i = 0; i < n; ++i)
      if (good_word[i]) {
        for (int j = 0; j < int((a[i]).size()); ++j) {
          ch_to_word[a[i][j]].push_back(i);
          ch_pos[i][a[i][j]] = j;
        }
      }
    auto SuffixLen = [&](int u, int v) -> int {
      auto uit = ch_pos[u].find(a[v][0]);
      if (uit == ch_pos[u].end()) return -1;
      int start = uit->second;
      int r1 = SA::rank[apos[u] + start];
      int r2 = SA::rank[apos[v]];
      if (r1 > r2) swap(r1, r2);
      int len = int((a[u]).size()) - start;
      if (SA::lcp[lcp_rmq.QueryIndex(r1 + 1, r2)] >= len) return len;
      return -1;
    };
    vector<pair<int, int>> pairs;
    pairs.reserve(k);
    for (int i = 1; i <= k; ++i) {
      int nword = int((ch_to_word[i]).size());
      assert(nword <= 2);
      if (nword < 2) continue;
      int u = ch_to_word[i][0], v = ch_to_word[i][1];
      if (u > v) swap(u, v);
      assert(u != v);
      pairs.emplace_back(u, v);
    }
    sort((pairs).begin(), (pairs).end());
    for (int i = 1; i < int((pairs).size()); ++i) {
      if (pairs[i] == pairs[i - 1]) {
        int u = pairs[i].first, v = pairs[i].second;
        good_ch[ds.Find(a[u][0])] = 0;
      }
    }
    for (int i = 0; i < n; ++i)
      good_word[i] = good_word[i] && good_ch[ds.Find(a[i][0])];
    for (auto& p : (pairs)) {
      int u = p.first, v = p.second;
      if (!good_ch[ds.Find(a[u][0])]) continue;
      int len = SuffixLen(u, v);
      42;
      if (len >= 0) g.AddEdge(u, v, len);
      len = SuffixLen(v, u);
      42;
      if (len >= 0) g.AddEdge(v, u, len);
    }
    for (int i = 0; i < n; ++i) {
      assert(int((g.adj[i]).size()) <= 1);
      if (0) {
        42;
        for (auto& e : (g.adj[i])) 42;
        42;
      }
    }
  }
  {
    vector<int> comp_id;
    vector<int> comp_sizes = g.TarjanScc(comp_id);
    for (int i = 0; i < n; ++i)
      if (good_word[i]) {
        if (comp_sizes[comp_id[i]] > 1) {
          good_ch[ds.Find(a[i][0])] = 0;
        }
      }
    for (int i = 0; i < n; ++i)
      good_word[i] = good_word[i] && good_ch[ds.Find(a[i][0])];
  }
  vector<int> cnt_len(max(k, m) + 1, 0);
  {
    vector<int> top_order = g.TopologicalSort();
    vector<char> vis(n, 0);
    for (auto& x : (top_order)) {
      if (!good_word[x] || vis[x]) continue;
      int u = x;
      int len = int((a[u]).size());
      vis[u] = 1;
      while (!g.adj[u].empty()) {
        int v = g.adj[u][0].first;
        vis[v] = 1;
        len += int((a[v]).size()) - g.adj[u][0].second;
        u = v;
      }
      cnt_len[len]++;
      good_ch[ds.Find(a[x][0])] = 0;
    }
    for (int i = 1; i <= k; ++i)
      if (good_ch[ds.Find(i)]) cnt_len[1]++;
  }
  vector<pair<int, int>> trans;
  for (int i = 1; i <= m; ++i)
    if (cnt_len[i] > 0) trans.emplace_back(i, cnt_len[i]);
  vector<ModInt> f(m + 1);
  f[0] = 1;
  for (int i = 1; i <= m; ++i) {
    f[i] = 0;
    for (auto& p : (trans)) {
      if (p.first > i) break;
      f[i] += f[i - p.first] * p.second;
    }
  }
  printf("%d\n", f[m].v);
}
int main() {
  int t = 1;
  for (int i = 1; i <= t; ++i) {
    Solve();
  }
  return 0;
}
