```go
package main
import (
"fmt"
)
func main() {
var n, m, k int
if _, err := fmt.Scan(&n, &m, &k); err != nil {
return
}
// dp[r] stores the maximum sum obtained from previous rows having remainder r modulo k
dp := make([]int, k)
for i := range dp {
dp[i] = -1
}
dp[0] = 0
for i := 0; i < n; i++ {
row := make([]int, m)
for j := 0; j < m; j++ {
fmt.Scan(&row[j])
}
limit := m / 2
// rowDP[c][r] stores max sum using exactly c elements from current row with remainder r
rowDP := make([][]int, limit+1)
for c := 0; c <= limit; c++ {
rowDP[c] = make([]int, k)
for r := 0; r < k; r++ {
rowDP[c][r] = -1
}
}
rowDP[0][0] = 0
for _, val := range row {
for c := limit; c >= 1; c-- {
for r := 0; r < k; r++ {
if rowDP[c-1][r] != -1 {
s := rowDP[c-1][r] + val
rem := s % k
if s > rowDP[c][rem] {
rowDP[c][rem] = s
}
}
}
}
}
// bestRow[r] stores the max sum from current row with remainder r (using <= m/2 elements)
bestRow := make([]int, k)
for r := 0; r < k; r++ {
bestRow[r] = -1
}
for c := 0; c <= limit; c++ {
for r := 0; r < k; r++ {
if rowDP[c][r] > bestRow[r] {
bestRow[r] = rowDP[c][r]
}
}
}
// Combine with global dp
newDP := make([]int, k)
for r := 0; r < k; r++ {
newDP[r] = -1
}
for r1 := 0; r1 < k; r1++ {
if dp[r1] == -1 {
continue
}
for r2 := 0; r2 < k; r2++ {
if bestRow[r2] == -1 {
continue
}
s := dp[r1] + bestRow[r2]
rem := s % k
if s > newDP[rem] {
newDP[rem] = s
}
}
}
dp = newDP
}
ans := dp[0]
if ans < 0 {
ans = 0
}
fmt.Println(ans)
}
```