package main
import (
"bufio"
"fmt"
"os"
)
const MOD int64 = 998244353
func addmod(a, b int64) int64 { a += b; if a >= MOD { a -= MOD }; return a }
func submod(a, b int64) int64 { a -= b; if a < 0 { a += MOD }; return a }
func mulmod(a, b int64) int64 { return (a * b) % MOD }
func powmod(a, e int64) int64 {
res := int64(1)
for e > 0 {
if e&1 == 1 {
res = (res * a) % MOD
}
a = (a * a) % MOD
e >>= 1
}
return res
}
func invmod(a int64) int64 { return powmod(a, MOD-2) }
func main() {
in := bufio.NewReader(os.Stdin)
var n, m, k int
if _, err := fmt.Fscan(in, &n, &m, &k); err != nil {
return
}
type Arr struct {
c int
v []int
}
arrs := make([]Arr, n)
appearsGe2 := make([]bool, k+1)
dupInSome := make([]bool, k+1)
for i := 0; i < n; i++ {
var c int
fmt.Fscan(in, &c)
arrs[i].c = c
arrs[i].v = make([]int, c)
cnt := make(map[int]int)
for j := 0; j < c; j++ {
fmt.Fscan(in, &arrs[i].v[j])
cnt[arrs[i].v[j]]++
}
if c >= 2 {
for _, x := range arrs[i].v {
appearsGe2[x] = true
}
}
dup := false
for _, t := range cnt {
if t > 1 {
dup = true
break
}
}
if dup {
for x := range cnt {
dupInSome[x] = true
}
}
}
// Build directed graph for arrays with c>=2 and without duplicates
out := make([]int, k+1)
inDeg := make([]int, k+1)
for i := 1; i <= k+1; i++ {
if i <= k {
out[i] = 0
inDeg[i] = 0
}
}
hasEdge := make([]bool, k+1)
bad := make([]bool, k+1)
for i := 0; i < n; i++ {
c := arrs[i].c
if c < 2 {
continue
}
// Check duplicates again
ok := true
seen := make(map[int]bool)
for _, x := range arrs[i].v {
if seen[x] {
ok = false
break
}
seen[x] = true
}
if !ok {
continue
}
for j := 0; j+1 < c; j++ {
u := arrs[i].v[j]
v := arrs[i].v[j+1]
// add edge u->v; but if conflicting multiple outgoing/incoming, mark bad
if out[u] == 0 {
out[u] = v
} else if out[u] != v {
bad[u] = true
bad[out[u]] = true
bad[v] = true
}
if out[u] != 0 {
hasEdge[u] = true
}
inDeg[v]++
if inDeg[v] > 1 {
bad[v] = true
}
}
}
// Any node appearing in any duplicate array is bad (forbidden)
for x := 1; x <= k; x++ {
if dupInSome[x] {
bad[x] = true
}
}
// Detect cycles among nodes with out[u]!=0
vis := make([]int8, k+1) // 0 unvisited, 1 visiting, 2 done
var dfs func(u int)
cycleNodes := make([]bool, k+1)
dfs = func(u int) {
vis[u] = 1
v := out[u]
if v != 0 {
if vis[v] == 0 {
dfs(v)
} else if vis[v] == 1 {
// cycle detected; mark cycle nodes
// walk until come back
x := v
for {
cycleNodes[x] = true
x = out[x]
if x == v || x == 0 {
break
}
}
}
}
vis[u] = 2
}
for u := 1; u <= k; u++ {
if hasEdge[u] && vis[u] == 0 {
dfs(u)
}
}
for u := 1; u <= k; u++ {
if cycleNodes[u] {
bad[u] = true
}
}
// Good components: path segments consisting of nodes with out possibly 0/1, inDeg<=1, not bad, and connected by edges
usedInEdges := make([]bool, k+1)
for u := 1; u <= k; u++ {
if hasEdge[u] || inDeg[u] > 0 {
usedInEdges[u] = true
}
}
visitedComp := make([]bool, k+1)
compSizes := make([]int, 0)
for u := 1; u <= k; u++ {
if usedInEdges[u] && !bad[u] {
// find path start: inDeg==0 or predecessor bad/none
if inDeg[u] == 0 {
// walk path
v := u
sz := 0
ok := true
for v != 0 && usedInEdges[v] && !visitedComp[v] {
if bad[v] {
ok = false
break
}
visitedComp[v] = true
sz++
v = out[v]
}
if ok {
compSizes = append(compSizes, sz)
}
}
}
}
// There may be remaining nodes that are not visited but have inDeg>0 (part of paths whose starts are bad or missing)
for u := 1; u <= k; u++ {
if usedInEdges[u] && !bad[u] && !visitedComp[u] {
// This should be a path piece with cycle or entering from bad; walk and mark but don't count
v := u
for v != 0 && usedInEdges[v] && !visitedComp[v] {
visitedComp[v] = true
v = out[v]
}
// do not add as good component
}
}
// Count free letters: not participating in any c>=2 array and not in any duplicate array
free := 0
for x := 1; x <= k; x++ {
if !appearsGe2[x] && !dupInSome[x] {
free++
}
}
// Precompute factorials up to k (sufficient for (free + number of components))
maxN := k
fact := make([]int64, maxN+5)
ifact := make([]int64, maxN+5)
fact[0] = 1
for i := 1; i <= maxN+4; i++ {
fact[i] = mulmod(fact[i-1], int64(i))
}
ifact[maxN+4] = invmod(fact[maxN+4])
for i := maxN + 4; i >= 1; i-- {
ifact[i-1] = mulmod(ifact[i], int64(i))
}
comb := func(n, r int) int64 {
if r < 0 || r > n {
return 0
}
return mulmod(fact[n], mulmod(ifact[r], ifact[n-r]))
}
// divisors of m
divs := make([]int, 0)
for d := 1; d*d <= m; d++ {
if m%d == 0 {
divs = append(divs, d)
if d*d != m {
divs = append(divs, m/d)
}
}
}
// base answer: all strings using only free letters (no constraints)
ans := powmod(int64(free), int64(m))
// Add contributions by taking ALL good components together (approximation)
sumComp := 0
for _, s := range compSizes {
sumComp += s
}
B := len(compSizes)
// If there are no good components, nothing extra beyond free^m is safely counted here
if B > 0 {
for _, L := range divs {
// Try to form period length L by using all components plus r free letters
if L < sumComp {
continue
}
r := L - sumComp
if r > free || r < 0 {
continue
}
// strings exist only if m % L == 0
if m%L != 0 {
continue
}
ways := comb(free, r)
blocks := B + r
ways = mulmod(ways, fact[blocks])
ans = addmod(ans, ways)
}
}
fmt.Println(ans % MOD)
}