unittest
{
	assert( [ "2 3", "0 1 1", "1 0 1", "1 4 0", "1 2", "3 3" ].parse.expand.solve == 3 );
	assert( [ "4 3", "0 12 71", "81 0 53", "14 92 0", "1 1 2 1", "2 1 1 2", "2 2 1 3", "1 1 2 2" ].parse.expand.solve == 428 );
}



import std.algorithm;
import std.conv;
import std.range;
import std.stdio;
import std.typecons;

void main()
{
	stdin.byLineCopy.parse.expand.solve.writeln;
}

auto parse( Range )( Range input )
if( isInputRange!Range && is( ElementType!Range == string ) )
{
	auto nc = input.front.split.to!( long[] );
	auto dss = new long[][]( nc[ 1 ], nc[ 1 ] );
	foreach( ref ds; dss )
	{
		input.popFront;
		ds = input.front.split.to!( long[] );
	}
	auto css = new long[][]( nc[ 0 ], nc[ 0 ] );
	foreach( ref cs; css )
	{
		input.popFront;
		cs = input.front.split.to!( long[] );
		cs[] -= 1;
	}
	return tuple( nc[ 1 ], dss, css );
}

auto solve( long cn, long[][] dss, long[][] css )
{
	// 各色・各マスごとの、塗り替えた後の違和感の合計を集計
	auto kss = new long[][]( cn, 3 );
	foreach( y, cs; css )
	{
		foreach( x, c; cs )
		{
			foreach( t; 0L .. cn )
			{
				kss[ t ][ ( x + y ) % 3 ] += dss[ c ][ t ];
			}
		}
	}
	
	// 違和感の少ない順に色を並べ替え
	auto sss = new Tuple!( long, long )[][]( cn, 3 );
	foreach( i, ks; kss )
	{
		auto ss = zip( ks, iota( 0L, cn ) ).array;
		ss.sort();
		sss[ i ] = ss.take( 3 );
	}
	
	// 違和感の少ない順に3色使ったときの最小値を検索
	auto ans = long.max;
	foreach( s1; sss[ 0 ] )
	{
		foreach( s2; sss[ 1 ] )
		{
			if( s1[ 1 ] == s2[ 1 ] ) continue;
			foreach( s3; sss[ 2 ] )
			{
				if( s1[ 1 ] == s3[ 1 ] || s2[ 1 ] == s3[ 1 ] ) continue;
				ans = min( ans, s1[ 0 ] + s2[ 0 ] + s3[ 0 ] );
			}
		}
	}
	return ans;
}
