#include using namespace std; struct XorShift { unsigned long long x; explicit XorShift(unsigned long long seed) : x(seed) {} unsigned int next() { x ^= x << 7; x ^= x >> 9; return (unsigned int)x; } }; int N, M; vector A; vector answer; bool solvePath(const vector &order) { vector label(M, 0); int currentLabel = 1; int sum = 0; for (int idx = 0; idx < M; idx++) { int v = order[idx]; label[v] = currentLabel; sum += A[v]; sum %= N; int remainingCells = M - idx - 1; int remainingGroups = N - currentLabel; if (currentLabel < N && sum == 0 && remainingCells >= remainingGroups) { currentLabel++; sum = 0; } } if (currentLabel == N && sum == 0) { answer.swap(label); return true; } return false; } bool trySnake(bool horizontal, bool reverseMajor, bool startForward) { vector order; order.reserve(M); for (int k = 0; k < N; k++) { int major = reverseMajor ? N - 1 - k : k; bool forward = (k % 2 == 0 ? startForward : !startForward); for (int t = 0; t < N; t++) { int minor = forward ? t : N - 1 - t; int r = horizontal ? major : minor; int c = horizontal ? minor : major; order.push_back(r * N + c); } } return solvePath(order); } pair originalCell(int r, int c, int mode) { switch (mode) { case 0: return {r, c}; case 1: return {r, N - 1 - c}; case 2: return {N - 1 - r, c}; case 3: return {N - 1 - r, N - 1 - c}; case 4: return {c, r}; case 5: return {c, N - 1 - r}; case 6: return {N - 1 - c, r}; default: return {N - 1 - c, N - 1 - r}; } } bool solveTree(const vector &parent, const vector &order) { vector sum = A; vector cut(M, 0); int root = order[0]; int componentCount = 1; for (int idx = M - 1; idx >= 0; idx--) { int v = order[idx]; if (v == root) continue; if (sum[v] == 0) { cut[v] = 1; componentCount++; } else { int p = parent[v]; sum[p] += sum[v]; sum[p] %= N; } } if (componentCount < N) return false; vector component(M, -1); vector componentParent; componentParent.reserve(componentCount); component[root] = 0; componentParent.push_back(-1); int created = 1; for (int idx = 1; idx < M; idx++) { int v = order[idx]; int p = parent[v]; if (cut[v]) { component[v] = created++; componentParent.push_back(component[p]); } else { component[v] = component[p]; } } vector childCount(componentCount, 0); for (int c = 1; c < componentCount; c++) childCount[componentParent[c]]++; queue leaves; for (int c = 1; c < componentCount; c++) { if (childCount[c] == 0) leaves.push(c); } vector removed(componentCount, 0); vector finalLabel(componentCount, N); int nextLabel = 1; while (nextLabel < N) { if (leaves.empty()) return false; int c = leaves.front(); leaves.pop(); if (removed[c] || childCount[c] != 0) continue; removed[c] = 1; finalLabel[c] = nextLabel++; int p = componentParent[c]; if (p >= 0) { childCount[p]--; if (p != 0 && !removed[p] && childCount[p] == 0) leaves.push(p); } } answer.assign(M, 0); for (int v = 0; v < M; v++) answer[v] = finalLabel[component[v]]; return true; } bool tryCombTree(int mode) { vector parent(M, -2), order; order.reserve(M); for (int r = 0; r < N; r++) { for (int c = 0; c < N; c++) { auto [origr, origc] = originalCell(r, c, mode); int v = origr * N + origc; order.push_back(v); if (r == 0 && c == 0) { parent[v] = -1; } else if (c == 0) { auto [pr, pc] = originalCell(r - 1, c, mode); parent[v] = pr * N + pc; } else { auto [pr, pc] = originalCell(r, c - 1, mode); parent[v] = pr * N + pc; } } } return solveTree(parent, order); } bool tryBackboneTree(int line, bool transpose, bool reverseMajor) { vector parent(M, -2), order; order.reserve(M); auto idCell = [&](int r, int c) { if (reverseMajor) r = N - 1 - r; if (transpose) swap(r, c); return r * N + c; }; auto addVertex = [&](int v, int p) { parent[v] = p; order.push_back(v); }; addVertex(idCell(0, line), -1); for (int r = 0; r < N; r++) { if (r > 0) addVertex(idCell(r, line), idCell(r - 1, line)); int prev = idCell(r, line); for (int c = line + 1; c < N; c++) { int v = idCell(r, c); addVertex(v, prev); prev = v; } prev = idCell(r, line); for (int c = line - 1; c >= 0; c--) { int v = idCell(r, c); addVertex(v, prev); prev = v; } } return solveTree(parent, order); } bool tryRandomDfs(unsigned long long seed) { XorShift rng(seed); vector parent(M, -2), order, st; order.reserve(M); st.reserve(M); int root = (int)(rng.next() % M); parent[root] = -1; order.push_back(root); st.push_back(root); const int dr[4] = {1, -1, 0, 0}; const int dc[4] = {0, 0, 1, -1}; while (!st.empty()) { int v = st.back(); int r = v / N; int c = v % N; int cand[4]; int cnt = 0; for (int dir = 0; dir < 4; dir++) { int nr = r + dr[dir]; int nc = c + dc[dir]; if (nr < 0 || N <= nr || nc < 0 || N <= nc) continue; int to = nr * N + nc; if (parent[to] == -2) cand[cnt++] = to; } if (cnt == 0) { st.pop_back(); continue; } int to = cand[rng.next() % cnt]; parent[to] = v; order.push_back(to); st.push_back(to); } return solveTree(parent, order); } bool tryRandomBfs(unsigned long long seed) { XorShift rng(seed); vector parent(M, -2), order; order.reserve(M); int root = (int)(rng.next() % M); parent[root] = -1; order.push_back(root); const int dr[4] = {1, -1, 0, 0}; const int dc[4] = {0, 0, 1, -1}; for (int head = 0; head < (int)order.size(); head++) { int v = order[head]; int r = v / N; int c = v % N; int dirs[4] = {0, 1, 2, 3}; for (int i = 3; i >= 1; i--) swap(dirs[i], dirs[rng.next() % (i + 1)]); for (int idx = 0; idx < 4; idx++) { int dir = dirs[idx]; int nr = r + dr[dir]; int nc = c + dc[dir]; if (nr < 0 || N <= nr || nc < 0 || N <= nc) continue; int to = nr * N + nc; if (parent[to] != -2) continue; parent[to] = v; order.push_back(to); } } return solveTree(parent, order); } bool connectedMask(int mask) { int first = __builtin_ctz(mask); int seen = 0; queue q; q.push(first); seen |= 1 << first; const int dr[4] = {1, -1, 0, 0}; const int dc[4] = {0, 0, 1, -1}; while (!q.empty()) { int v = q.front(); q.pop(); int r = v / N; int c = v % N; for (int dir = 0; dir < 4; dir++) { int nr = r + dr[dir]; int nc = c + dc[dir]; if (nr < 0 || N <= nr || nc < 0 || N <= nc) continue; int to = nr * N + nc; if ((mask >> to & 1) && !(seen >> to & 1)) { seen |= 1 << to; q.push(to); } } } return seen == mask; } bool solveExactSmall() { if (N > 4) return false; int full = (1 << M) - 1; vector> candidates(M); for (int mask = 1; mask <= full; mask++) { int sum = 0; for (int v = 0; v < M; v++) { if (mask >> v & 1) sum = (sum + A[v]) % N; } if (sum != 0 || !connectedMask(mask)) continue; int first = __builtin_ctz(mask); candidates[first].push_back(mask); } for (auto &v : candidates) { sort(v.begin(), v.end(), [](int x, int y) { return __builtin_popcount((unsigned)x) < __builtin_popcount((unsigned)y); }); } vector chosen; unordered_set bad; function dfs = [&](int rest, int groupsLeft) -> bool { if (groupsLeft == 0) return rest == 0; if (__builtin_popcount((unsigned)rest) < groupsLeft) return false; long long key = ((long long)rest << 3) | groupsLeft; if (bad.count(key)) return false; int first = __builtin_ctz(rest); for (int mask : candidates[first]) { if ((mask & rest) != mask) continue; chosen.push_back(mask); if (dfs(rest ^ mask, groupsLeft - 1)) return true; chosen.pop_back(); } bad.insert(key); return false; }; if (!dfs(full, N)) return false; answer.assign(M, 0); for (int id = 0; id < (int)chosen.size(); id++) { int mask = chosen[id]; for (int v = 0; v < M; v++) { if (mask >> v & 1) answer[v] = id + 1; } } return true; } void solveCase() { cin >> N; M = N * N; A.resize(M); int total = 0; for (int i = 0; i < M; i++) { int x; cin >> x; A[i] = x % N; total += A[i]; total %= N; } if (total != 0) { cout << "-1\n"; return; } if (solveExactSmall()) { // done } else { bool ok = false; for (int horizontal = 0; horizontal < 2 && !ok; horizontal++) { for (int reverseMajor = 0; reverseMajor < 2 && !ok; reverseMajor++) { for (int startForward = 0; startForward < 2 && !ok; startForward++) { ok = trySnake(horizontal, reverseMajor, startForward); } } } for (int mode = 0; mode < 8 && !ok; mode++) ok = tryCombTree(mode); vector backboneLines; if (N <= 50) { for (int line = 0; line < N; line++) backboneLines.push_back(line); } else { int cand[] = {0, N - 1, N / 2, (N - 1) / 2, N / 3, (2 * N) / 3}; for (int line : cand) { if (0 <= line && line < N) backboneLines.push_back(line); } sort(backboneLines.begin(), backboneLines.end()); backboneLines.erase(unique(backboneLines.begin(), backboneLines.end()), backboneLines.end()); } for (int line : backboneLines) { for (int transpose = 0; transpose < 2 && !ok; transpose++) { for (int reverseMajor = 0; reverseMajor < 2 && !ok; reverseMajor++) { ok = tryBackboneTree(line, transpose, reverseMajor); } } if (ok) break; } int attempts = 4; for (int t = 0; t < attempts && !ok; t++) { ok = tryRandomDfs(0x9e3779b97f4a7c15ULL + 1000003ULL * t + N); } for (int t = 0; t < attempts && !ok; t++) { ok = tryRandomBfs(0xbf58476d1ce4e5b9ULL + 1000033ULL * t + N); } if (!ok) { cout << "-1\n"; return; } } for (int r = 0; r < N; r++) { for (int c = 0; c < N; c++) { if (c) cout << ' '; cout << answer[r * N + c]; } cout << '\n'; } } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int T; cin >> T; while (T--) solveCase(); return 0; }