// Original C++ solution (slower):
#include <iostream>

#include <fstream>

#include <cmath>  

#include <cstdlib>

#include <ctime>

#include <algorithm>

#include <numeric>

#include <functional>

#include <string> 

#include <vector>

#include <bitset>

#include <map>

#include <set>

#include <stack>

#include <queue>

#include <deque>



using namespace std;

using ll = long long;

using ull = unsigned long long;

using ld = long double;



#define REP(i,n) for(int i = 0; i < int(n); i++)

#define REREP(i,n) for(int i = int(n - 1); i >= 0; i--)

#define FOR(i, m, n) for(int i = int(m);i < int(n);i++)

#define REFOR(i, m, n) for(int i = int(n - 1);i >= int(m);i--)

#define ALL(obj) (obj).begin(),(obj).end()



#define VI vector<int>

#define VVI vector<vector<int>>

#define VVVI vector<vector<vector<int>>>

#define VD vector<double>

#define VVD vector<vector<double>>

#define VVVD vector<vector<vector<double>>>

#define VL vector<ll>

#define VVL vector<vector<ll>>

#define VVVL vector<vector<vector<ll>>>

#define VB vector<bool>

#define VVB vector<vector<bool>>

#define VVVB vector<vector<vector<bool>>>

#define VS vector<string>

#define VVS vector<vector<string>>

#define VVVS vector<vector<vector<string>>>



#define PII pair<int,int>

#define PDD pair<double,double>

#define PLL pair<ll,ll>

#define VPII vector<pair<int,int>>

#define VVPII vector<vector<pair<int,int>>>

#define VVVPII vector<vector<vector<pair<int,int>>>>

#define VPLL vector<pair<ll,ll>>

#define VVPLL vector<vector<pair<ll,ll>>>

#define VVVPLL vector<vector<vector<pair<ll,ll>>>>



#define SUMI(obj) accumulate((obj).begin(), (obj).end(), 0)

#define SUMD(obj) accumulate((obj).begin(), (obj).end(), 0.)

#define SUML(obj) accumulate((obj).begin(), (obj).end(), 0LL)

#define SORT(obj) sort((obj).begin(), (obj).end()) // 1,2,3,4,5...

#define RESORT(obj) sort((obj).begin(), (obj).end(), greater<int>()) // 5,4,3,2,1...

#define UB(obj,n) upper_bound((obj).begin(), (obj).end(), n) //itr > n

#define LB(obj,n) lower_bound((obj).begin(), (obj).end(), n) //itr>= n



const ll MOD = (ll)1e9 + 7;

const ll HINF = (ll)1e18;

const ll LINF = (ll)1e9;

const double PI = 4 * atan(1);



//integer

class Integer {

public:

	int gcd(int a, int b) {

		if (a == 0 || b == 0) return 0;

		if (a < b) swap(a, b);

		while (b != 0) {

			a = a%b;

			swap(a, b);

		}

		return a;

	}



	int lcm(int a, int b) {

		if (a == 0 || b == 0) return 0;

		return a / gcd(a, b)*b;

	}

};



//geometry

class Geometry {

public:

	double dist(double x1, double y1, double x2, double y2) {

		return sqrt((x1 - x2)*(x1 - x2) + (y1 - y2)*(y1 - y2));

	}



	bool intersection_judgment(double ax, double ay, double bx, double by, double cx, double cy, double dx, double dy) {

		double ta = (cx - dx) * (ay - cy) + (cy - dy) * (cx - ax);

		double tb = (cx - dx) * (by - cy) + (cy - dy) * (cx - bx);

		double tc = (ax - bx) * (cy - ay) + (ay - by) * (ax - cx);

		double td = (ax - bx) * (dy - ay) + (ay - by) * (ax - dx);

		return tc * td < 0 && ta * tb < 0;

	}



};



//eratosthenes

class Eratosthenes {

public:

	vector<bool> primary(int N) {

		vector<bool> primary(N + 1, true);

		primary[0] = false;

		if (N == 0) return primary;

		primary[1] = false;

		for (int i = 1; i*i <= N; i++) if (primary[i]) for (int j = 2 * i; j <= N; j += i) primary[j] = false;

		return primary;

	}

};



//combination mod

class Combination_Mod {

public:

	VL factorial;



	//N! table

	void set(ll N, ll quotient) {

		factorial.resize(N + 1, 1);

		for (ll i = 1; i <= N; i++) {

			factorial[i] *= factorial[i - 1] * i;

			factorial[i] %= quotient;

		}

		return;

	}



	//repeat square method y = x^n mod quotient 

	ll rsm(ll x, ll n, ll quotient) {

		ll y = 1;

		while (n > 0) {

			if ((n & 1) == 1) {

				y *= x;

				y %= quotient;

			}

			x *= x;

			x %= quotient;

			n >>= 1;

		}

		return y;

	}



	//modulus of nCk - combination mod 

	ll nCk(ll n, ll k, ll quotient) {

		if (n < k) return 0;

		if (k == 0) return 1;

		ll nCk = (factorial[n] % quotient);

		nCk *= rsm(factorial[k], quotient - 2, quotient);

		nCk %= quotient;

		nCk *= rsm(factorial[n - k], quotient - 2, quotient);

		return (nCk % quotient);

	}

};



//combination

class Combination {

public:

	VVL nCk;

	VVD nCkP;



	void set(int N) {

		nCk.resize(N + 1);

		REP(i, N + 1) nCk[i].resize(N + 1, 0);



		nCk[0][0] = 1;

		for (int n = 1; n <= N; n++) {

			for (int k = 0; k <= n; k++) {

				if (k == 0) nCk[n][k] = nCk[n - 1][k];

				else nCk[n][k] = nCk[n - 1][k - 1] + nCk[n - 1][k];



			}

		}

	}



	void setP(int N) {

		nCkP.resize(N + 1);

		REP(i, N + 1) nCkP[i].resize(N + 1, 0.);



		nCkP[0][0] = 1;

		for (int n = 1; n <= N; n++) {

			for (int k = 0; k <= n; k++) {

				if (k == 0) nCkP[n][k] = nCkP[n - 1][k] / 2.;

				else nCkP[n][k] = (nCkP[n - 1][k - 1] + nCkP[n - 1][k]) / 2.;

			}

		}

	}



};



//union-find

class Union_Find {

public:

	VI node;

	void set(int n) {

		node.resize(n);

		REP(i, n) node[i] = i;

	}



	int root(int n) {

		if (node[n] == n) return n;

		else return node[n] = root(node[n]);

	}



	void unite(int n, int m) {

		if (n > m) swap(n, m);

		n = root(n);

		m = root(m);

		if (n == m) return;

		else node[m] = n;

	}

};



//dijkstra

class Dijkstra {

public:

	VVPII edge;

	VVI dist;

	void set(int N, int inf) {

		edge.resize(N);

		dist.resize(N);

		REP(i, N) dist[i].resize(N);

		REP(i, N)REP(j, N) dist[i][j] = inf;

	}



	void make_edge(int from, int to, int cost) {

		edge[from].push_back({ to,cost });

	}



	void dijkstra(int start) {

		priority_queue<PII, vector<PII>, greater<PII>> pq;

		dist[start][start] = 0;

		pq.push({ 0,start });

		while (!pq.empty()) {

			int from = pq.top().second;

			pq.pop();

			REP(i, edge[from].size()) {

				int to = edge[from][i].first;

				if (dist[start][to] > dist[start][from] + edge[from][i].second) {

					dist[start][to] = dist[start][from] + edge[from][i].second;

					pq.push({ dist[start][from] + edge[from][i].second,to });

				}

			}

		}

	}

};



//bellman ford

class Bellman_Ford {

public:

	struct edge {

		int from, to, cost;

	};

	int N;

	VI node;

	vector<edge> graph;



	void set(int node_, int inf) {

		N = node_;

		node.resize(node_);

	}



	void make_edge(int from, int to, int cost) {

		graph.push_back({ from,to,cost });

	}



	void bellman_ford(int start, int inf) {

		REP(i, N) node[i] = inf;

		node[start] = 0;

		REP(i, N - 1) REP(j, graph.size()) if (node[graph[j].to] > node[graph[j].from] + graph[j].cost) node[graph[j].to] = node[graph[j].from] + graph[j].cost;

	}

};



//ford fulkerson

class Ford_Fulkerson {

public:

	struct edge {

		int to, cap, rev;

	};



	int node, start, goal;

	vector<vector<edge>> graph;

	vector<bool> visit;



	void set(int node_, int start_, int goal_) {

		node = node_;

		start = start_;

		goal = goal_;

		graph.resize(node);

		visit.resize(node);

	}



	void make_edge(int from, int to, int cap) {

		graph[from].push_back({ to,cap,(int)graph[to].size() });

		graph[to].push_back({ from,0,(int)graph[from].size() - 1 });

	}



	int dfs(int from, int flow) {

		if (from == goal) return flow;

		visit[from] = false;

		REP(i, graph[from].size()) {

			if (visit[graph[from][i].to] && (graph[from][i].cap > 0)) {

				int dflow = dfs(graph[from][i].to, min(flow, graph[from][i].cap));

				if (dflow > 0) {

					graph[from][i].cap -= dflow;

					graph[graph[from][i].to][graph[from][i].rev].cap += dflow;

					return dflow;

				}

			}

		}

		return 0;

	}



	int maxflow(void) {

		int maxflow = 0;

		while (1) {

			REP(i, graph.size()) visit[i] = true;

			int flow = dfs(start, 1);

			if (flow == 0) return maxflow;

			maxflow += flow;

		}

	}

};



//longest increasing subsequence

class Longest_Increasing_Subsequence {

public:

	int N;

	VI dp, ar;



	void set(int N_) {

		N = N_;

		dp.resize(N, LINF);

		ar.resize(N);// ar has to be set value.

	}



	int make_lis(void) {

		REP(i, N) *lower_bound(ALL(dp), ar[i]) = ar[i];

		return distance(dp.begin(), lower_bound(ALL(dp), LINF));

	}



};



//least common ancestor

class Least_Common_Ancestor {

public:

	int node;

	int MAXLOG2;

	VVI edge;

	VI depth;

	VVI parent;



	void set(int node_, int MAXLOG2_) {

		node = node_;

		MAXLOG2 = MAXLOG2_;

		edge.resize(node);

		depth.resize(node);

		parent.resize(node);

		REP(i, node) parent[i].resize(MAXLOG2);

	}



	void dfs(int root, int root_from) {

		depth[root] = 0;

		parent[root][0] = root_from;

		stack<pair<int, int>> st;

		st.push({ root,root_from });



		while (!st.empty()) {

			int from = st.top().first;

			int from_from = st.top().second;

			st.pop();

			REP(i, edge[from].size()) {

				int to = edge[from][i];

				if (to != from_from) {

					depth[to] = depth[from] + 1;

					parent[to][0] = from;

					st.push({ to,from });

				}

			}

		}

		return;

	}



	//make table

	void initialize(int N) {

		REP(k, MAXLOG2 - 1) {

			REP(i, N) {

				if (parent[i][k] < 0) parent[i][k + 1] = -1;

				else parent[i][k + 1] = parent[parent[i][k]][k];

			}

		}

		return;

	}



	//get least common ancestor

	int get(int a, int b) {

		if (depth[a] > depth[b]) swap(a, b);

		REP(k, MAXLOG2 - 1) if (((depth[b] - depth[a]) >> k) & 1) b = parent[b][k];

		if (a == b) return a;

		REREP(k, MAXLOG2) {

			if (parent[a][k] != parent[b][k]) {

				a = parent[a][k];

				b = parent[b][k];

			}

		}

		return parent[a][0];

	}

};



//directed acyclic graph

class Directed_Acyclic_Graph {

public:



	int N;

	Dijkstra dijkstra;

	struct edge {

		int dest, cost;

		bool need;

	};

	vector<vector<edge>> output;

	VI input;

	VI topological_order;



	void set(int N_, int inf) {

		N = N_;

		dijkstra.set(N, inf);

		output.resize(N);

		input.resize(N, 0);

	}



	void make_edge(int from, int to, int cost) {

		dijkstra.make_edge(from, to, cost);

		output[from].push_back({ to, cost, true });

	}



	void make_dag(int start) {

		dijkstra.dijkstra(start);

		REP(i, N) {

			REP(j, output[i].size()) {

				int from = i;

				edge &e = output[i][j];

				if (dijkstra.dist[start][e.dest] - dijkstra.dist[start][from] != e.cost) e.need = false;

			}

		}

	}



	bool topological_sort(int start) {

		REP(i, N) REP(j, output[i].size()) if (output[i][j].need) input[output[i][j].dest]++;



		stack<int> st;

		st.push(start);

		while (!st.empty()) {

			int from = st.top();

			st.pop();

			topological_order.push_back(from);

			REP(i, output[from].size()) {

				edge &e = output[from][i];

				if (e.need) {

					input[e.dest]--;

					if (input[e.dest] == 0) st.push(e.dest);

				}

			}

		}



		bool valid = true;

		REP(i, N) if (input[i] > 0) valid = false;

		return valid;

	}

};



void YN(bool flag) {

	cout << ((flag) ? "YES" : "NO") << endl;

}



void Yn(bool flag) {

	cout << ((flag) ? "Yes" : "No") << endl;

}



void yn(bool flag) {

	cout << ((flag) ? "yes" : "no") << endl;

}



VI a(3);

int N, C,ans = LINF;

VVI D(30, VI(30)), c(500, VI(500)), E(30,VI(3,0));



void dfs(int i){

	if(i < 3){

		REP(j,C){

			a[i] = j + 1;

			dfs(i + 1);

		}

	}

	if(i == 3 && a[0] != a[1] && a[1] != a[2] && a[2] != a[0] ){

		ans = min(ans, E[a[0] - 1][0] + E[a[1] - 1][1] + E[a[2] - 1][2]);

	}

}



int main() {

	cin >> N >> C;

	

	REP(i, C) REP(j, C) cin >> D[i][j];

	REP(i, N) REP(j, N) cin >> c[i][j];

	REP(i, C) REP(k, N) REP(l, N) E[i][(k+l)%3] += D[c[k][l] - 1][i];



	dfs(0);

	cout << ans << endl;

	return 0;

}


// Optimized solution to the same problem (faster):
#include <bits/stdc++.h>

using namespace std;

using ll = long long;

using ull = unsigned long long;



#define REP(i,n) for(long long i = 0; i < (n); i++)

#define FOR(i, m, n) for(long long i = (m);i < (n); ++i)

#define ALL(obj) (obj).begin(),(obj).end()

#define SPEED cin.tie(0);ios::sync_with_stdio(false);



template<class T> using V = vector<T>;

template<class T, class U> using P = pair<T, U>;

template<class T> using PQ = priority_queue<T>;

template<class T> using PQR = priority_queue<T,vector<T>,greater<T>>;



const ll MOD = (ll)1e9 + 7;

const ll MOD2 = 998244353;

const ll HIGHINF = (ll)1e18;

const ll LOWINF = (ll)1e15;

const long double PI = 3.1415926535897932384626433;



template<typename T> vector<T> make_v(size_t N,T init){return vector<T>(N,init);}

template<typename... T> auto make_v(size_t N,T... t){return vector<decltype(make_v(t...))>(N,make_v(t...));}

template <class T> void corner(bool flg, T hoge) {if (flg) {cout << hoge << endl; exit(0);}}

template <class T, class U>ostream &operator<<(ostream &o, const map<T, U>&obj) {o << "{"; for (auto &x : obj) o << " {" << x.first << " : " << x.second << "}" << ","; o << " }"; return o;}

template <class T>ostream &operator<<(ostream &o, const set<T>&obj) {o << "{"; for (auto itr = obj.begin(); itr != obj.end(); ++itr) o << (itr != obj.begin() ? ", " : "") << *itr; o << "}"; return o;}

template <class T>ostream &operator<<(ostream &o, const multiset<T>&obj) {o << "{"; for (auto itr = obj.begin(); itr != obj.end(); ++itr) o << (itr != obj.begin() ? ", " : "") << *itr; o << "}"; return o;}

template <class T>ostream &operator<<(ostream &o, const vector<T>&obj) {o << "{"; for (int i = 0; i < (int)obj.size(); ++i)o << (i > 0 ? ", " : "") << obj[i]; o << "}"; return o;}

template <class T, class U>ostream &operator<<(ostream &o, const pair<T, U>&obj) {o << "{" << obj.first << ", " << obj.second << "}"; return o;}

template <template <class tmp>  class T, class U> ostream &operator<<(ostream &o, const T<U> &obj) {o << "{"; for (auto itr = obj.begin(); itr != obj.end(); ++itr)o << (itr != obj.begin() ? ", " : "") << *itr; o << "}"; return o;}

void print(void) {cout << endl;}

template <class Head> void print(Head&& head) {cout << head;print();}

template <class Head, class... Tail> void print(Head&& head, Tail&&... tail) {cout << head << " ";print(forward<Tail>(tail)...);}

template <class T> void chmax(T& a, const T b){a=max<T>(a,b);}

template <class T> void chmin(T& a, const T b){a=min<T>(a,b);}

void YN(bool flg) {cout << ((flg) ? "YES" : "NO") << endl;}

void Yn(bool flg) {cout << ((flg) ? "Yes" : "No") << endl;}

void yn(bool flg) {cout << ((flg) ? "yes" : "no") << endl;}



int main() {

	ll N,C; cin >> N >> C;

	V<V<ll>> D(C,V<ll>(C)),E(N,V<ll>(N));

	for(int i = 0; i < C; ++i) for(int j = 0; j < C; ++j) cin >> D[i][j];

	for(int i = 0; i < N; ++i) for(int j = 0; j < N; ++j) cin >> E[i][j];

	for(int i = 0; i < N; ++i) for(int j = 0; j < N; ++j) E[i][j]--;

	//for(int k = 0; k < C; ++k) for(int i = 0; i < C; ++i) for(int j = 0; j < C; ++j) chmin(D[i][j],D[i][k]+D[k][j]);



	V<V<ll>> sum(3,V<ll>(C,0));

	for(int i = 0; i < N; ++i) 

	for(int j = 0; j < N; ++j) 

	for(int k = 0; k < C; ++k) sum[(i+j)%3][k] += D[E[i][j]][k];



	ll ans = LOWINF;

	for(int i = 0; i < C; ++i){

		for(int j = 0; j < C; ++j){

			for(int k = 0; k < C; ++k){

				if(i==j||j==k||k==i) continue;

				chmin(ans,sum[0][i]+sum[1][j]+sum[2][k]);

			}

		}

	}

	cout << ans << endl;

	



    return 0;

}


// Fastest known solution for this problem: