#ifndef __BIPARTITE_H__
#define __BIPARTITE_H__

#include <vector>
#include <queue>

// お借りしています: https://ei1333.github.io/luzhiled/snippets/graph/hopcroft-karp.html
struct HopcroftKarp {
	std::vector<std::vector<int>> graph;
	std::vector<int> dist, match;
	std::vector<bool> used, vv;

	HopcroftKarp(int n, int m) : graph(n), match(m, -1), used(n) {}

	void add_edge(int u, int v) {
		graph[u].push_back(v);
	}

	void bfs() {
		dist.assign(graph.size(), -1);
		std::queue<int> que;
		for (int i = 0; i < graph.size(); i++) {
			if (!used[i]) {
				que.emplace(i);
				dist[i] = 0;
			}
		}

		while (!que.empty()) {
			int a = que.front();
			que.pop();
			for (auto &b : graph[a]) {
				int c = match[b];
				if (c >= 0 && dist[c] == -1) {
					dist[c] = dist[a] + 1;
					que.emplace(c);
				}
			}
		}
	}

	bool dfs(int a) {
		vv[a] = true;
		for (auto &b : graph[a]) {
			int c = match[b];
			if (c < 0 || (!vv[c] && dist[c] == dist[a] + 1 && dfs(c))) {
				match[b] = a;
				used[a] = true;
				return (true);
			}
		}
		return (false);
	}

	int bipartite_matching() {
		int ret = 0;
		while (true) {
			bfs();
			vv.assign(graph.size(), false);
			int flow = 0;
			for (int i = 0; i < graph.size(); i++) {
				if (!used[i] && dfs(i)) ++flow;
			}
			if (flow == 0) return (ret);
			ret += flow;
		}
	}

	std::vector<std::pair<int, int>> output() {
		std::vector<std::pair<int, int>> ret;
		for (int i = 0; i < match.size(); i++) {
			if (match[i] == -1) continue;
			ret.push_back(std::make_pair(match[i], i));
		}
		return ret;
	}
};

struct BipartiteGraphEdge {
	int from;	// index for source
	int to;		// index for sink

	BipartiteGraphEdge();
	BipartiteGraphEdge(int from, int to);
	BipartiteGraphEdge(const BipartiteGraphEdge&) = default;
	BipartiteGraphEdge& operator=(const BipartiteGraphEdge&) = default;
};

class BipartiteGraph {
public:
	using Edge = BipartiteGraphEdge;

private:
	int source_size_m;
	int sink_size_m;
	std::vector<Edge> edges_m;
public:
	BipartiteGraph();
	BipartiteGraph(int source_size, int sink_size);
	void Add(int from, int to);

	int Source_size() const;
	int Sink_size() const;
	const std::vector<Edge>& Edges() const;
};

class BipartiteSolver {
public:
	enum class Part {
		Source,
		Sink
	};

	struct Vertex {
		Part part;
		int index;
		Vertex() = default;
		Vertex(Part part, int index);
	};

	using Edge = BipartiteGraph::Edge;

private:
	BipartiteGraph graph_m;
	std::vector<Edge> matching_m;
	std::vector<bool> marked_source_m;
	std::vector<bool> marked_sink_m;

public:
	BipartiteSolver(const BipartiteGraph& graph);
	void Solve();

	std::vector<Edge> Maximum_matching() const;
	std::vector<Vertex> Minimum_vertex_cover() const;
	std::vector<Vertex> Maximum_stable_set() const;
};


#endif
#ifdef ONLY_MY_ENVIR
#include "Bipartite.h"
#endif

#include <cassert>
#include <stack>

BipartiteGraphEdge::BipartiteGraphEdge() 
	: BipartiteGraphEdge(0, 0) {
}

BipartiteGraphEdge::BipartiteGraphEdge(int from, int to) 
	: from(from), to(to) {
}

BipartiteGraph::BipartiteGraph() 
	: BipartiteGraph(0, 0) {
}

BipartiteGraph::BipartiteGraph(int source_size, int sink_size)
	: source_size_m(source_size), sink_size_m(sink_size) {
}

void BipartiteGraph::Add(int from, int to) {
	assert(from >= 0 && from < source_size_m);
	assert(to >= 0 && to < sink_size_m);

	edges_m.push_back(Edge(from, to));
}

int BipartiteGraph::Source_size() const {
	return source_size_m;
}

int BipartiteGraph::Sink_size() const {
	return sink_size_m;
}

const std::vector<BipartiteGraph::Edge>& BipartiteGraph::Edges() const {
	return edges_m;
}

BipartiteSolver::BipartiteSolver(const BipartiteGraph& graph)
	: graph_m(graph) {
}

BipartiteSolver::Vertex::Vertex(Part part, int index)
	: part(part), index(index) {
}

void BipartiteSolver::Solve() {
	HopcroftKarp solver(graph_m.Source_size(), graph_m.Sink_size());
	for (const auto& edge : graph_m.Edges()) {
		solver.add_edge(edge.from, edge.to);
	}

	solver.bipartite_matching();
	auto res = solver.output();

	std::vector<Edge> matching;
	for (const auto& pair : res) {
		matching.push_back(Edge(pair.first, pair.second));
	}

	matching_m = matching;
	
	/*------- ここから点被覆とかの計算 -------*/
	const int M = graph_m.Source_size();
	const int N = graph_m.Sink_size();

	std::vector<std::vector<int>> source_to_sink(M);
	std::vector<std::vector<int>> sink_to_source(N);
	for (const auto& edge : matching_m) {
		int from = edge.from;
		int to = edge.to;

		sink_to_source[to].push_back(from);
	}

	/* もう少し考えよう */
	for (const auto& edge : graph_m.Edges()) {
		int from = edge.from;
		int to = edge.to;

		int destination = sink_to_source[to].empty() ? -1 : sink_to_source[to].front();
		if (from == destination) continue;
		source_to_sink[from].push_back(to);
	}

	std::vector<bool> used_source(M, false);
	std::vector<bool> used_sink(N, false);

	// (part, index)
	std::stack<Vertex> stack;
	std::vector<bool> start_source(M, true);
	for (const auto& edge : matching_m) {
		start_source[edge.from] = false;
	}

	for (int i = 0; i < start_source.size(); ++i) {
		if (start_source[i]) {
			stack.push(Vertex(Part::Source, i));
		}
	}
	
	while (!stack.empty()) {
		auto vertex = stack.top(); stack.pop();
		Part part = vertex.part;
		int index = vertex.index;

		if (part == Part::Source) {
			if (used_source[index]) continue;
			used_source[index] = true;

			for (int to : source_to_sink[index]) {
				stack.push(Vertex(Part::Sink, to));
			}
		}
		
		if (part == Part::Sink) {
			if (used_sink[index]) continue;
			used_sink[index] = true;

			for (int to : sink_to_source[index]) {
				stack.push(Vertex(Part::Source, to));
			}
		}
	}

	marked_source_m = used_source;
	marked_sink_m = used_sink;
}

std::vector<BipartiteSolver::Edge> BipartiteSolver::Maximum_matching() const {
	return matching_m;
}

std::vector<BipartiteSolver::Vertex> BipartiteSolver::Minimum_vertex_cover() const {
	std::vector<Vertex> vertices;
	for (int i = 0; i < marked_source_m.size(); ++i) {
		if (!marked_source_m[i]) {
			vertices.push_back(Vertex(Part::Source, i));
		}
	}

	for (int i = 0; i < marked_sink_m.size(); ++i) {
		if (marked_sink_m[i]) {
			vertices.push_back(Vertex(Part::Sink, i));
		}
	}
	return vertices;
}

std::vector<BipartiteSolver::Vertex> BipartiteSolver::Maximum_stable_set() const {
	std::vector<Vertex> vertices;
	for (int i = 0; i < marked_source_m.size(); ++i) {
		if (marked_source_m[i]) {
			vertices.push_back(Vertex(Part::Source, i));
		}
	}

	for (int i = 0; i < marked_sink_m.size(); ++i) {
		if (!marked_sink_m[i]) {
			vertices.push_back(Vertex(Part::Sink, i));
		}
	}
	return vertices;
}
#define _CRT_SECURE_NO_WARNINGS
#define _SCL_SECURE_NO_WARNINGS
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cassert>
#include <iostream>
#include <string>
#include <vector>
#include <list>
#include <utility>
#include <algorithm>
#include <functional>
#include <cmath>
#include <stack>
#include <queue>
#include <set>
#include <map>
#include <iomanip>
#include <sstream>
#include <bitset>
#include <limits>
#include <numeric>
#include <valarray>
#include <fstream>

using namespace std;
typedef unsigned int uint;
typedef long long LL;
typedef unsigned long long ULL;
typedef pair<LL, LL> PP;
#define REP(i, a, n) for(LL i = (a), i##_max = (n); i < i##_max; ++i)
#define REM(i, a, n) for(LL i = (LL)(n) - 1, i##min = (a); i >= i##min; --i)
#define ALL(arr) (arr).begin(), (arr).end()
#define FLOAT fixed << setprecision(16)
#define SPEEDUP {cin.tie(NULL); ios::sync_with_stdio(false);}
const int INF = 0x3FFFFFFF;
const LL INFLL = 0x3FFFFFFF3FFFFFFF;
const double INFD = 1.0e+308;
const double EPS = 1.0e-9;

void YesNo(bool b) { cout << (b ? "Yes" : "No") << endl; }
void YESNO(bool b) { cout << (b ? "YES" : "NO") << endl; }
template <class T, class U>
istream& operator>>(istream& ist, pair<T, U>& right) { return ist >> right.first >> right.second; }
template <class T, class U>
ostream& operator<<(ostream& ost, const pair<T, U>& right) { return ost << right.first << ' ' << right.second; }
template <class T, class TCompatible, size_t N>
void Fill(T(&dest)[N], const TCompatible& val) { fill(dest, dest + N, val); }
template <class T, class TCompatible, size_t M, size_t N>
void Fill(T(&dest)[M][N], const TCompatible& val) { for (int i = 0; i < M; ++i) Fill(dest[i], val); }
template<class T>
T Compare(T left, T right) { return left > right ? 1 : (left < right ? -1 : 0); }
istream& Ignore(istream& ist) { string s; ist >> s; return ist; }
bool Inside(int i, int j, int h, int w) { return i >= 0 && i < h && j >= 0 && j < w; }
template <class T>
T Next() { T buf; cin >> buf; return buf; }

#ifdef ONLY_MY_ENVIR
#include "IntMod.h"
#include "BinaryMatrix.h"
#include "BIT.h"
#include "Factorization.h"
#include "FlowSolver.h"
#include "Graph.h"
#include "LazySegmentTree.h"
#include "Math.h"
#include "Matrix.h"
#include "MinMax.h"
#include "Position.h"
#include "Range.h"
#include "Rational.h"
#include "SegmentTree.h"
#include "SegmentTree2D.h"
#include "SuffixArray.h"
#include "Tree.h"
#include "UnionFind.h"
#include "MinCostFlowSolver.h"
#include "Bipartite.h"
#endif

#ifdef __GNUC__
typedef __int128 LLL;
istream& operator>> (istream& ist, __int128& val) { LL tmp;  ist >> tmp; val = tmp; return ist; }
ostream& operator<< (ostream& ost, __int128 val) { LL tmp = val; ost << tmp; return ost; }
#endif

#if 1234567891
#include <array>
#include <random>
#include <unordered_set>
#include <unordered_map>
template<typename T>
using PriorityQ = priority_queue<T, vector<T>, greater<T> >;
// template <class T>
// auto Is(const T& value) { return [value](const auto& comparand) -> bool { return comparand == value; }; }
#endif

int N, M;
int A[100][100];
int C[100][100];
vector<int> V[100][100];

std::vector<int> sub(const BipartiteGraph& graph) {
	BipartiteSolver solver(graph);
	solver.Solve();
	auto res = solver.Maximum_matching();

	std::vector<int> ret(N);
	for (auto p : res) {
		ret[p.from] = p.to;
	}
	return ret;
}

int B[100][100];
int Q[100][100];
int main() {
	cin >> N >> M;
	REP(i, 0, N) {
		REP(j, 0, M) {
			cin >> A[i][j];
			--A[i][j];
		}
	}
	
	REP(i, 0, N) {
		REP(j, 0, M) {
			int t = A[i][j] / M;
			++C[i][t];
			V[i][t].push_back(A[i][j]);
		}
	}
	
	REP(z, 0, M) {
		BipartiteGraph graph(N, N);
		REP(i, 0, N) {
			REP(j, 0, N) {
				if (C[i][j]) {
					graph.Add(i, j);
				}
			}
		}
		auto res = sub(graph);
		REP(i, 0, N) {
			--C[i][res[i]];
		}
		
		REP(i, 0, N) {
			int j = res[i];
			int v = V[i][j].back();
			V[i][j].pop_back();
			B[i][z] = v;
		}
	}

	REP(i, 0, N) {
		REP(j, 0, M) {
			cout << B[i][j] + 1 << ' ';
		}
		cout << endl;
	}

	REP(j, 0, M) {
		std::vector<int> arr;
		REP(i, 0, N) {
			arr.push_back(B[i][j]);
		}
		sort(arr.begin(), arr.end());

		REP(i, 0, N) {
			Q[i][j] = arr[i];
		}
	}

	REP(i, 0, N) {
		REP(j, 0, M) {
			cout << Q[i][j] + 1 << ' ';
		}
		cout << endl;
	}


	return 0;
}

Measured memory_bytes: 1328