import java.io.OutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.StringTokenizer;
import java.io.IOException;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.InputStream;

/**
 * Built using CHelper plug-in
 * Actual solution is at the top
 *
 * @author dmytro.prytula
 */
public class Main {
    public static void main(String[] args) {
        InputStream inputStream = System.in;
        OutputStream outputStream = System.out;
        InputReader in = new InputReader(inputStream);
        PrintWriter out = new PrintWriter(outputStream);
        DTriFiguri solver = new DTriFiguri();
        solver.solve(1, in, out);
        out.close();
    }

    static class DTriFiguri {
        static int N;
        int M = 305;
        int INF = (int) 1e9;
        DTriFiguri.Point[] possibleKnight = new DTriFiguri.Point[]{
                DTriFiguri.Point.of(2, 1),
                DTriFiguri.Point.of(-2, 1),
                DTriFiguri.Point.of(2, -1),
                DTriFiguri.Point.of(-2, -1),
                DTriFiguri.Point.of(1, 2),
                DTriFiguri.Point.of(-1, 2),
                DTriFiguri.Point.of(1, -2),
                DTriFiguri.Point.of(-1, -2)
        };
        DTriFiguri.Point[][] dist = new DTriFiguri.Point[M][M];

        {

        }

        private boolean in(DTriFiguri.Point p) {
            return 0 <= p.x && p.x < N && 0 <= p.y && p.y < N;
        }

        public void solve(int testNumber, InputReader in, PrintWriter out) {

            N = in.nextInt();
            DTriFiguri.Point[] pos = new DTriFiguri.Point[N * N];
            int[][] A = new int[N][N];
            for (int x = 0; x < N; x++) {
                for (int y = 0; y < N; y++) {
                    A[x][y] = in.nextInt() - 1;
                    pos[A[x][y]] = DTriFiguri.Point.of(x, y);

                }
            }

            for (int i = 0; i < M; i++) {
                for (int j = 0; j < M; j++) {
//                Arrays.fill(dist[i], Point.of(INF, INF));
                    dist[i][j] = DTriFiguri.Point.of(INF, INF);
                }
            }
            for (int i = 0; i < M; i++)
//            for (int j = 0; j < M; j++)
//                if (i == j)
                dist[i][i] = DTriFiguri.Point.of(0, 0);

            for (int x = 0; x < N; x++) {
                for (int y = 0; y < N; y++) {
                    //knight
                    for (int i = 0; i < possibleKnight.length; i++) {
                        int nx = x + possibleKnight[i].x;
                        int ny = y + possibleKnight[i].y;
                        if (in(DTriFiguri.Point.of(nx, ny))) {
                            dist[DTriFiguri.Point.hash(x, y, 0)][DTriFiguri.Point.hash(nx, ny, 0)] = DTriFiguri.Point.of(1, 0);
                        }
                    }
                    //bishop
                    for (int i = -N + 1; i <= N - 1; i++) {
                        int nx = x + i;
                        int ny = y + i;
                        if (in(DTriFiguri.Point.of(nx, ny)))
                            dist[DTriFiguri.Point.hash(x, y, 1)][DTriFiguri.Point.hash(nx, ny, 1)] = DTriFiguri.Point.of(1, 0);

                        ny = y - i;
                        if (in(DTriFiguri.Point.of(nx, ny)))
                            dist[DTriFiguri.Point.hash(x, y, 1)][DTriFiguri.Point.hash(nx, ny, 1)] = DTriFiguri.Point.of(1, 0);
                    }

                    //rook
                    for (int i = 0; i < N; i++) {
                        int nx = i;
                        int ny = y;
//                    if (in(Point.of(nx, ny)))
                        dist[DTriFiguri.Point.hash(x, y, 2)][DTriFiguri.Point.hash(nx, ny, 2)] = DTriFiguri.Point.of(1, 0);
                        nx = x;
                        ny = i;
//                    if (in(Point.of(nx, ny)))
                        dist[DTriFiguri.Point.hash(x, y, 2)][DTriFiguri.Point.hash(nx, ny, 2)] = DTriFiguri.Point.of(1, 0);

                    }

                    for (int i = 0; i < 3; i++) {
                        for (int j = 0; j < 3; j++) {
                            if (i != j) {
                                dist[DTriFiguri.Point.hash(x, y, i)][DTriFiguri.Point.hash(x, y, j)] = DTriFiguri.Point.of(1, 1);
                            }
                        }
                    }

                }
            }

            for (int k = 0; k < M; k++) {
                for (int i = 0; i < M; i++) {
                    for (int j = 0; j < M; j++) {
                        dist[i][j] = DTriFiguri.Point.min(dist[i][j], DTriFiguri.Point.sum(dist[i][k], dist[k][j]));
                    }
                }
            }

            DTriFiguri.Point[][] dp = new DTriFiguri.Point[N * N][3];
            for (int i = 0; i < N * N; i++) {
                for (int j = 0; j < 3; j++) {
                    dp[i][j] = DTriFiguri.Point.of(INF, INF);
                }
            }

            dp[0][0] = dp[0][1] = dp[0][2] = DTriFiguri.Point.of(0, 0);

            for (int i = 0; i < N * N - 1; i++)
                for (int j = 0; j < 3; j++)
                    for (int k = 0; k < 3; k++)
                        dp[i + 1][k] = DTriFiguri.Point.min(
                                dp[i + 1][k],
                                DTriFiguri.Point.sum(dp[i][j], dist[DTriFiguri.Point.hash(pos[i].x, pos[i].y, j)][DTriFiguri.Point.hash(pos[i + 1].x, pos[i + 1].y, k)])
                        );

            DTriFiguri.Point ans = DTriFiguri.Point.of(INF, INF);
            ans = DTriFiguri.Point.min(ans, dp[N * N - 1][0]);
            ans = DTriFiguri.Point.min(ans, dp[N * N - 1][1]);
            ans = DTriFiguri.Point.min(ans, dp[N * N - 1][2]);

            out.print(ans.toString());
        }

        private static class Point {
            int x;
            int y;

            private Point(int x, int y) {
                this.x = x;
                this.y = y;
            }

            public static DTriFiguri.Point of(int x, int y) {
                return new DTriFiguri.Point(x, y);
            }

            public static int hash(int x, int y, int p) {
                return x * 10 * 3 + y * 3 + p;
            }

            public static DTriFiguri.Point min(DTriFiguri.Point a, DTriFiguri.Point b) {
                if (a.x == b.x) {
                    return a.y < b.y ? a : b;
                }
                return a.x < b.x ? a : b;
            }

            public static DTriFiguri.Point sum(DTriFiguri.Point point1, DTriFiguri.Point point) {
                return DTriFiguri.Point.of(point1.x + point.x, point1.y + point.y);
            }

            public String toString() {
                return x + " " + y;
            }

        }

    }

    static class InputReader {
        public BufferedReader reader;
        public StringTokenizer tokenizer;

        public InputReader(InputStream stream) {
            reader = new BufferedReader(new InputStreamReader(stream), 32768);
            tokenizer = null;
        }

        public String nextToken() {
            while (tokenizer == null || !tokenizer.hasMoreTokens()) {
                try {
                    tokenizer = new StringTokenizer(reader.readLine());
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
            return tokenizer.nextToken();
        }

        public int nextInt() {
            return Integer.parseInt(nextToken());
        }

    }
}

import java.awt.event.MouseAdapter;
import java.io.*;
import java.lang.reflect.Field;
import java.util.*;

public class Main {

    public static void main(String[] args) throws IOException {
        Reader.init(System.in);
        new Main();
    }

    Main() throws IOException {
        solve();
    }

    void solve() throws IOException {
        PrintWriter out = new PrintWriter(System.out);

        int N = Reader.nextInt();

        PriorityQueue<Node> P = new PriorityQueue<>(new Comparator<Node>() {
            @Override
            public int compare(Node o1, Node o2) {
                if(o1.st == o2.st){return o1.rep - o2.rep;}
                return o1.st - o2.st;
            }
        });

        int[][][][] steps = new int[N+1][N+1][N*N + 1][3];  // [x][y][val][type]  type - 0(bishop) 1(rook) 2(knight)
        int[][][][] reps = new int[N+1][N+1][N*N+1][3];
        boolean[][][][] vis = new boolean[N+1][N+1][N*N+1][3];

        int[][][] move = new int[3][][];
        move[0] = new int[][]{{1,1}, {-1,-1}, {1,-1}, {-1,1}};
        move[1] = new int[][]{{0,1}, {0,-1}, {1,0}, {-1,0}};
        move[2] = new int[][]{{1,2}, {2,1}, {-1,-2}, {-2,-1}, {-2,1}, {1,-2}, {-1,2}, {2,-1}};

        int[][] pos = new int[N*N+1][2];
        for(int i = 1; i <= N; i++){
            for(int j = 1; j <= N; j++){
                int v = Reader.nextInt();
                pos[v] = new int[]{i,j};
            }
        }

        P.add(new Node(1,pos[1][0], pos[1][1],0, 0,0));
        P.add(new Node(1,pos[1][0], pos[1][1],0, 0,1));
        P.add(new Node(1,pos[1][0], pos[1][1],0, 0,2));

        while(!P.isEmpty()){
             Node S = P.poll();
             if(S.x <= 0 || S.x > N || S.y <= 0 || S.y > N){continue;}

             if(vis[S.x][S.y][S.val][S.type]) continue;

             steps[S.x][S.y][S.val][S.type] = S.st;
             reps[S.x][S.y][S.val][S.type] = S.rep;
             vis[S.x][S.y][S.val][S.type] =  true;

            if(S.x == pos[S.val+1][0] && S.y == pos[S.val+1][1]){
                S.val++;
                if(S.val == N*N){out.println(S.st + " " + S.rep); out.close(); return;}
                steps[S.x][S.y][S.val][S.type] = S.st;
                reps[S.x][S.y][S.val][S.type] = S.rep;
                vis[S.x][S.y][S.val][S.type] =  true;
            }

            // replacement
            for(int i = 0; i < 3; i++){
                P.add(new Node(S.val,S.x,S.y,S.rep + 1, S.st+1, i));
            }

            //move
            for(int[] m : move[S.type]){
                if(S.type == 2){
                    for(int i = 0; i < m.length; i++){
                        P.add(new Node(S.val,S.x+m[0],S.y+m[1],S.rep, S.st+1, S.type));
                    }
                }
                else{
                    for(int i = 1; i <= N; i++){
                        P.add(new Node(S.val,S.x+m[0]*i,S.y+m[1]*i,S.rep, S.st+1, S.type));
                    }
                }
            }
        }

        out.close();
    }

    class Node {

        int val;
        int x;
        int y;
        int rep; //count for replacements
        int st; //steps
        int type;

        Node(int val, int x, int y, int rep, int st, int type){
            this.val = val;
            this.x = x;
            this.y = y;
            this.rep = rep;
            this.st = st;
            this.type = type;
        }

    }

}

class Reader {
    static BufferedReader reader;
    static StringTokenizer tokenizer;

    static void init(InputStream input) {
        reader = new BufferedReader(new InputStreamReader(input) );
        tokenizer = new StringTokenizer("");

    }

    static String nextToken() {
        while (tokenizer == null || !tokenizer.hasMoreTokens()) {
            try {

                tokenizer = new StringTokenizer(reader.readLine());

            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        return tokenizer.nextToken();
    }

    static String next() throws IOException {

        while ( ! tokenizer.hasMoreTokens() ) {

            tokenizer = new StringTokenizer(

                    reader.readLine() );

        }
        return tokenizer.nextToken();

    }

    static int nextInt() throws IOException {

        return Integer.parseInt( next() );

    }

    static double nextDouble() throws IOException {

        return Double.parseDouble( next() );

    }

    static long nextLong() {

        return Long.parseLong(nextToken());
    }
}

//package baobab;

import java.io.*;
import java.util.*;

public class D {

    public static void main(String[] args) {
        Solver solver = new Solver();
    }

    static class Solver {
        IO io;

        public Solver() {
            this.io = new IO();
            try {
                solve();
            } catch (RuntimeException e) {
                if (!e.getMessage().equals("Clean exit")) {
                    throw e;
                }
            } finally {
                io.close();
            }
        }

        /****************************** START READING HERE ********************************/

        void solve() {
            int n = io.nextInt();
            int[][] b = new int[n][n];
            for (int y=0; y<n; y++) {
                for (int x=0; x<n; x++) {
                    b[y][x] = io.nextInt();
                }
            }
            int end = n*n;
            int[] x = new int[end+1];
            int[] y = new int[end+1];
            for (int i=0; i<n; i++) {
                for (int j=0; j<n; j++) {
                    int v = b[i][j];
                    y[v] = i;
                    x[v] = j;
                }
            }

            State ans = null;
            State[][][][][] leastSteps = new State[n][n][n][n][3];
            Deque<State> q = new ArrayDeque<>();
            q.add(new State(y[1], x[1], y[2], x[2], ROOK, 0, 0));
            q.add(new State(y[1], x[1], y[2], x[2], KNIGHT, 0, 0));
            q.add(new State(y[1], x[1], y[2], x[2], BISHOP, 0, 0));
            while (!q.isEmpty()) {
                State s = q.pollFirst();
                if (s.currY < 0 || s.currX < 0 || s.currY >= n || s.currX >= n) continue;
                State prev = leastSteps[s.currY][s.currX][s.goalY][s.goalX][s.piece];
                if (prev != null) {
                    if (prev.steps < s.steps) continue;
                    if (prev.steps == s.steps && prev.replacements <= s.replacements) continue;
                }
                leastSteps[s.currY][s.currX][s.goalY][s.goalX][s.piece] = s;

                // intermediary goal reached?
                if (s.currX == s.goalX && s.currY == s.goalY) {

                    if (b[s.currY][s.currX] == end) {
                        // actually reached end state (not necessarily with fewest steps because different piece = different state)
                        if (ans == null) ans = s; // first time reaching end state
                        else if (s.steps < ans.steps) ans = s; // reaching end state with this piece is faster than reaching end state with some other piece
                        else if (s.steps == ans.steps && s.replacements < ans.replacements) ans = s; // so far lowest steps & lowest replacements for that step size
                        continue; // stop bfs but dont break because we may find new solution with same steps but fewer replacements
                    }

                    // change state to set next goal.
                    int prevGoal = b[s.goalY][s.goalX];
                    int newGoal = prevGoal + 1;
                    int newGoalX = x[newGoal];
                    int newGoalY = y[newGoal];
                    // front of the queue!
                    q.addFirst(new State(s.goalY, s.goalX, newGoalY, newGoalX, s.piece, s.steps, s.replacements));
                    continue;
                }

                // change piece?
                q.addLast(new State(s.currY, s.currX, s.goalY, s.goalX, ROOK, s.steps+1, s.replacements+1));
                q.addLast(new State(s.currY, s.currX, s.goalY, s.goalX, BISHOP, s.steps+1, s.replacements+1));
                q.addLast(new State(s.currY, s.currX, s.goalY, s.goalX, KNIGHT, s.steps+1, s.replacements+1));

                // move current piece?
                if (s.piece == ROOK) {
                    for (int newY=0; newY<n; newY++) q.addLast(new State(newY, s.currX, s.goalY, s.goalX, s.piece, s.steps+1, s.replacements));
                    for (int newX=0; newX<n; newX++) q.addLast(new State(s.currY, newX, s.goalY, s.goalX, s.piece, s.steps+1, s.replacements));
                } else if (s.piece == KNIGHT) {
                    int newY = s.currY + 1;
                    int newX = s.currX + 2;
                    q.addLast(new State(newY, newX, s.goalY, s.goalX, s.piece, s.steps+1, s.replacements));
                    newY = s.currY + 1;
                    newX = s.currX - 2;
                    q.addLast(new State(newY, newX, s.goalY, s.goalX, s.piece, s.steps+1, s.replacements));
                    newY = s.currY - 1;
                    newX = s.currX - 2;
                    q.addLast(new State(newY, newX, s.goalY, s.goalX, s.piece, s.steps+1, s.replacements));
                    newY = s.currY - 1;
                    newX = s.currX + 2;
                    q.addLast(new State(newY, newX, s.goalY, s.goalX, s.piece, s.steps+1, s.replacements));
                    newY = s.currY + 2;
                    newX = s.currX + 1;
                    q.addLast(new State(newY, newX, s.goalY, s.goalX, s.piece, s.steps+1, s.replacements));
                    newY = s.currY - 2;
                    newX = s.currX + 1;
                    q.addLast(new State(newY, newX, s.goalY, s.goalX, s.piece, s.steps+1, s.replacements));
                    newY = s.currY + 2;
                    newX = s.currX - 1;
                    q.addLast(new State(newY, newX, s.goalY, s.goalX, s.piece, s.steps+1, s.replacements));
                    newY = s.currY - 2;
                    newX = s.currX - 1;
                    q.addLast(new State(newY, newX, s.goalY, s.goalX, s.piece, s.steps+1, s.replacements));

                } else if (s.piece == BISHOP) {
                    int newY = s.currY;
                    int newX = s.currX;
                    while (true) {
                        newY += 1;
                        newX += 1;
                        if (newX >= n || newX < 0 || newY >= n || newY < 0) break;
                        q.addLast(new State(newY, newX, s.goalY, s.goalX, s.piece, s.steps+1, s.replacements));
                    }
                    newY = s.currY;
                    newX = s.currX;
                    while (true) {
                        newY += 1;
                        newX -= 1;
                        if (newX >= n || newX < 0 || newY >= n || newY < 0) break;
                        q.addLast(new State(newY, newX, s.goalY, s.goalX, s.piece, s.steps+1, s.replacements));
                    }
                    newY = s.currY;
                    newX = s.currX;
                    while (true) {
                        newY -= 1;
                        newX -= 1;
                        if (newX >= n || newX < 0 || newY >= n || newY < 0) break;
                        q.addLast(new State(newY, newX, s.goalY, s.goalX, s.piece, s.steps+1, s.replacements));
                    }
                    newY = s.currY;
                    newX = s.currX;
                    while (true) {
                        newY -= 1;
                        newX += 1;
                        if (newX >= n || newX < 0 || newY >= n || newY < 0) break;
                        q.addLast(new State(newY, newX, s.goalY, s.goalX, s.piece, s.steps+1, s.replacements));
                    }
                }
            }

            io.println(ans.steps + " " + ans.replacements);
        }

        static int ROOK = 0;
        static int KNIGHT = 1;
        static int BISHOP = 2;

        class State {
            int currY;
            int currX;
            int goalY;
            int goalX;
            int piece;
            int steps;
            int replacements;

            public State(int currY, int currX, int goalY, int goalX, int piece, int steps, int replacements) {
                this.currY = currY;
                this.currX = currX;
                this.goalY = goalY;
                this.goalX = goalX;
                this.piece = piece;
                this.steps = steps;
                this.replacements = replacements;
            }

            @Override
            public boolean equals(Object o) {
                if (this == o) return true;
                if (o == null || getClass() != o.getClass()) return false;
                State state = (State) o;
                return currY == state.currY &&
                        currX == state.currX &&
                        goalY == state.goalY &&
                        goalX == state.goalX &&
                        piece == state.piece;
            }

            @Override
            public int hashCode() {
                return Objects.hash(currY, currX, goalY, goalX, piece);
            }
        }

        /************************** UTILITY CODE BELOW THIS LINE **************************/

        long MOD = (long)1e9 + 7;

        boolean closeToZero(double v) {
            // Check if double is close to zero, considering precision issues.
            return Math.abs(v) <= 0.0000000001;
        }

        class DrawGrid {

            void draw(boolean[][] d) {
                System.out.print("  ");
                for (int x=0; x<d[0].length; x++) {
                    System.out.print(" " + x + " ");
                }
                System.out.println("");
                for (int y=0; y<d.length; y++) {
                    System.out.print(y + " ");
                    for (int x=0; x<d[0].length; x++) {
                        System.out.print((d[y][x] ? "[x]" : "[ ]"));
                    }
                    System.out.println("");
                }
            }

            void draw(int[][] d) {
                int max = 1;
                for (int y=0; y<d.length; y++) {
                    for (int x=0; x<d[0].length; x++) {
                        max = Math.max(max, ("" + d[y][x]).length());
                    }
                }
                System.out.print("  ");
                String format = "%" + (max+2) + "s";
                for (int x=0; x<d[0].length; x++) {
                    System.out.print(String.format(format, x) + " ");
                }
                format = "%" + (max) + "s";
                System.out.println("");
                for (int y=0; y<d.length; y++) {
                    System.out.print(y + " ");
                    for (int x=0; x<d[0].length; x++) {
                        System.out.print(" [" + String.format(format, (d[y][x])) + "]");
                    }
                    System.out.println("");
                }
            }

        }

        class IDval implements Comparable<IDval> {
            int steps;
            long replacements;

            public IDval(int steps, long replacements) {
                this.replacements = replacements;
                this.steps = steps;
            }

            @Override
            public int compareTo(IDval o) {
                if (this.replacements < o.replacements) return -1;
                if (this.replacements > o.replacements) return 1;
                return this.steps - o.steps;
            }
        }

        private class ElementCounter {
            private HashMap<Long, Integer> elements;

            public ElementCounter() {
                elements = new HashMap<>();
            }

            public void add(long element) {
                int count = 1;
                Integer prev = elements.get(element);
                if (prev != null) count += prev;
                elements.put(element, count);
            }

            public void remove(long element) {
                int count = elements.remove(element);
                count--;
                if (count > 0) elements.put(element, count);
            }

            public int get(long element) {
                Integer val = elements.get(element);
                if (val == null) return 0;
                return val;
            }

            public int size() {
                return elements.size();
            }
        }

        class StringCounter {
            HashMap<String, Integer> elements;

            public StringCounter() {
                elements = new HashMap<>();
            }

            public void add(String identifier) {
                int count = 1;
                Integer prev = elements.get(identifier);
                if (prev != null) count += prev;
                elements.put(identifier, count);
            }

            public void remove(String identifier) {
                int count = elements.remove(identifier);
                count--;
                if (count > 0) elements.put(identifier, count);
            }

            public long get(String identifier) {
                Integer val = elements.get(identifier);
                if (val == null) return 0;
                return val;
            }

            public int size() {
                return elements.size();
            }
        }

        class DisjointSet {
            /** Union Find / Disjoint Set data structure. */
            int[] size;
            int[] parent;
            int componentCount;

            public DisjointSet(int n) {
                componentCount = n;
                size = new int[n];
                parent = new int[n];
                for (int i=0; i<n; i++) parent[i] = i;
                for (int i=0; i<n; i++) size[i] = 1;
            }

            public void join(int a, int b) {
                /* Find roots */
                int rootA = parent[a];
                int rootB = parent[b];
                while (rootA != parent[rootA]) rootA = parent[rootA];
                while (rootB != parent[rootB]) rootB = parent[rootB];

                if (rootA == rootB) {
                    /* Already in the same set */
                    return;
                }

                /* Merge smaller set into larger set. */
                if (size[rootA] > size[rootB]) {
                    size[rootA] += size[rootB];
                    parent[rootB] = rootA;
                } else {
                    size[rootB] += size[rootA];
                    parent[rootA] = rootB;
                }
                componentCount--;
            }

        }

        class Trie {
            int N;
            int Z;
            int nextFreeId;
            int[][] pointers;
            boolean[] end;

            /** maxLenSum = maximum possible sum of length of words */
            public Trie(int maxLenSum, int alphabetSize) {
                this.N = maxLenSum;
                this.Z = alphabetSize;
                this.nextFreeId = 1;
                pointers = new int[N+1][alphabetSize];
                end = new boolean[N+1];
            }

            public void addWord(String word) {
                int curr = 0;
                for (int j=0; j<word.length(); j++) {
                    int c = word.charAt(j) - 'a';
                    int next = pointers[curr][c];
                    if (next == 0) {
                        next = nextFreeId++;
                        pointers[curr][c] = next;
                    }
                    curr = next;
                }
                end[curr] = true;
            }

            public boolean hasWord(String word) {
                int curr = 0;
                for (int j=0; j<word.length(); j++) {
                    int c = word.charAt(j) - 'a';
                    int next = pointers[curr][c];
                    if (next == 0) return false;
                    curr = next;
                }
                return end[curr];
            }

        }

        private static class Prob {

            /** For heavy calculations on probabilities, this class
             *  provides more accuracy & efficiency than doubles.
             *  Math explained: https://en.wikipedia.org/wiki/Log_probability
             *  Quick start:
             *      - Instantiate probabilities, eg. Prob a = new Prob(0.75)
             *      - add(), multiply() return new objects, can perform on nulls & NaNs.
             *      - get() returns probability as a readable double */

            /** Logarithmized probability. Note: 0% represented by logP NaN. */
            private double logP;

            /** Construct instance with real probability. */
            public Prob(double real) {
                if (real > 0) this.logP = Math.log(real);
                else this.logP = Double.NaN;
            }

            /** Construct instance with already logarithmized value. */
            static boolean dontLogAgain = true;
            public Prob(double logP, boolean anyBooleanHereToChooseThisConstructor) {
                this.logP = logP;
            }

            /** Returns real probability as a double. */
            public double get() {
                return Math.exp(logP);
            }

            @Override
            public String toString() {
                return ""+get();
            }

            /***************** STATIC METHODS BELOW ********************/

            /** Note: returns NaN only when a && b are both NaN/null. */
            public static Prob add(Prob a, Prob b) {
                if (nullOrNaN(a) && nullOrNaN(b)) return new Prob(Double.NaN, dontLogAgain);
                if (nullOrNaN(a)) return copy(b);
                if (nullOrNaN(b)) return copy(a);

                double x = Math.max(a.logP, b.logP);
                double y = Math.min(a.logP, b.logP);
                double sum = x + Math.log(1 + Math.exp(y - x));
                return new Prob(sum, dontLogAgain);
            }

            /** Note: multiplying by null or NaN produces NaN (repping 0% real prob). */
            public static Prob multiply(Prob a, Prob b) {
                if (nullOrNaN(a) || nullOrNaN(b)) return new Prob(Double.NaN, dontLogAgain);
                return new Prob(a.logP + b.logP, dontLogAgain);
            }

            /** Returns true if p is null or NaN. */
            private static boolean nullOrNaN(Prob p) {
                return (p == null || Double.isNaN(p.logP));
            }

            /** Returns a new instance with the same value as original. */
            private static Prob copy(Prob original) {
                return new Prob(original.logP, dontLogAgain);
            }
        }

        class Binary implements Comparable<Binary> {

            /**
             * Use example: Binary b = new Binary(Long.toBinaryString(53249834L));
             *
             * When manipulating small binary strings, instantiate new Binary(string)
             * When just reading large binary strings, instantiate new Binary(string,true)
             * get(int i) returns a character '1' or '0', not an int.
             */

            private boolean[] d;
            private int first; // Starting from left, the first (most remarkable) '1'
            public int length;

            public Binary(String binaryString) {
                this(binaryString, false);
            }
            public Binary(String binaryString, boolean initWithMinArraySize) {
                length = binaryString.length();
                int size = Math.max(2*length, 1);
                first = length/4;
                if (initWithMinArraySize) {
                    first = 0;
                    size = Math.max(length, 1);
                }
                d = new boolean[size];
                for (int i=0; i<length; i++) {
                    if (binaryString.charAt(i) == '1') d[i+first] = true;
                }
            }

            public void addFirst(char c) {
                if (first-1 < 0) doubleArraySize();
                first--;
                d[first] = (c == '1' ? true : false);
                length++;
            }

            public void addLast(char c) {
                if (first+length >= d.length) doubleArraySize();
                d[first+length] = (c == '1' ? true : false);
                length++;
            }

            private void doubleArraySize() {
                boolean[] bigArray = new boolean[(d.length+1) * 2];
                int newFirst = bigArray.length / 4;
                for (int i=0; i<length; i++) {
                    bigArray[i + newFirst] = d[i + first];
                }
                first = newFirst;
                d = bigArray;
            }

            public boolean flip(int i) {
                boolean value = (this.d[first+i] ? false : true);
                this.d[first+i] = value;
                return value;
            }

            public void set(int i, char c) {
                boolean value = (c == '1' ? true : false);
                this.d[first+i] = value;
            }

            public char get(int i) {
                return (this.d[first+i] ? '1' : '0');
            }

            @Override
            public int compareTo(Binary o) {
                if (this.length != o.length) return this.length - o.length;
                int len = this.length;
                for (int i=0; i<len; i++) {
                    int diff = this.get(i) - o.get(i);
                    if (diff != 0) return diff;
                }
                return 0;
            }

            @Override
            public String toString() {
                StringBuilder sb = new StringBuilder();
                for (int i=0; i<length; i++) {
                    sb.append(d[i+first] ? '1' : '0');
                }
                return sb.toString();
            }

        }

        /************************** Range queries **************************/

        class FenwickMin {
            long n;
            long[] original;
            long[] bottomUp;
            long[] topDown;

            public FenwickMin(int n) {
                this.n = n;
                original = new long[n+2];
                bottomUp = new long[n+2];
                topDown = new long[n+2];
            }

            public void set(int modifiedNode, long value) {
                long replaced = original[modifiedNode];
                original[modifiedNode] = value;
                // Update left tree
                int i = modifiedNode;
                long v = value;
                while (i <= n) {
                    if (v > bottomUp[i]) {
                        if (replaced == bottomUp[i]) {
                            v = Math.min(v, original[i]);
                            for (int r=1 ;; r++) {
                                int x = (i&-i)>>>r;
                                if (x == 0) break;
                                int child = i-x;
                                v = Math.min(v, bottomUp[child]);
                            }
                        } else break;
                    }
                    if (v == bottomUp[i]) break;
                    bottomUp[i] = v;
                    i += (i&-i);
                }
                // Update right tree
                i = modifiedNode;
                v = value;
                while (i > 0) {
                    if (v > topDown[i]) {
                        if (replaced == topDown[i]) {
                            v = Math.min(v, original[i]);
                            for (int r=1 ;; r++) {
                                int x = (i&-i)>>>r;
                                if (x == 0) break;
                                int child = i+x;
                                if (child > n+1) break;
                                v = Math.min(v, topDown[child]);
                            }
                        } else break;
                    }
                    if (v == topDown[i]) break;
                    topDown[i] = v;
                    i -= (i&-i);
                }
            }

            public long getMin(int a, int b) {
                long min = original[a];
                int prev = a;
                int curr = prev + (prev&-prev); // parent right hand side
                while (curr <= b) {
                    min = Math.min(min, topDown[prev]); // value from the other tree
                    prev = curr;
                    curr = prev + (prev&-prev);;
                }
                min = Math.min(min, original[prev]);
                prev = b;
                curr = prev - (prev&-prev); // parent left hand side
                while (curr >= a) {
                    min = Math.min(min,bottomUp[prev]); // value from the other tree
                    prev = curr;
                    curr = prev - (prev&-prev);
                }
                return min;
            }

        }

        class FenwickSum {
            public long[] d;

            public FenwickSum(int n) {
                d=new long[n+1];
            }

            /** a[0] must be unused. */
            public FenwickSum(long[] a) {
                d=new long[a.length];
                for (int i=1; i<a.length; i++) {
                    modify(i, a[i]);
                }
            }

            /** Do not modify i=0. */
            void modify(int i, long v) {
                while (i<d.length) {
                    d[i] += v;
                    // Move to next uplink on the RIGHT side of i
                    i += (i&-i);
                }
            }

            /** Returns sum from a to b, *BOTH* inclusive. */
            long getSum(int a, int b) {
                return getSum(b) - getSum(a-1);
            }

            private long getSum(int i) {
                long sum = 0;
                while (i>0) {
                    sum += d[i];
                    // Move to next uplink on the LEFT side of i
                    i -= (i&-i);
                }
                return sum;
            }
        }

        class SegmentTree {

            /* Provides log(n) operations for:
             * - Range query (sum, min or max)
             * - Range update ("+8 to all values between indexes 4 and 94")
             */

            int N;
            long[] lazy;
            long[] sum;
            long[] min;
            long[] max;
            boolean supportSum;
            boolean supportMin;
            boolean supportMax;

            public SegmentTree(int n) {
                this(n, true, true, true);
            }

            public SegmentTree(int n, boolean supportSum, boolean supportMin, boolean supportMax) {
                for (N=2; N<n;) N*=2;
                this.lazy = new long[2*N];
                this.supportSum = supportSum;
                this.supportMin = supportMin;
                this.supportMax = supportMax;
                if (this.supportSum) this.sum = new long[2*N];
                if (this.supportMin) this.min = new long[2*N];
                if (this.supportMax) this.max = new long[2*N];
            }

            void modifyRange(long x, int a, int b) {
                modifyRec(a, b, 1, 0, N-1, x);
            }

            void setRange() {
                //TODO
            }

            long getSum(int a, int b) {
                return querySum(a, b, 1, 0, N-1);
            }

            long getMin(int a, int b) {
                return queryMin(a, b, 1, 0, N-1);
            }

            long getMax(int a, int b) {
                return queryMax(a, b, 1, 0, N-1);
            }

            private long querySum(int wantedLeft, int wantedRight, int i, int actualLeft, int actualRight) {
                if (wantedLeft > actualRight || wantedRight < actualLeft) {
                    return 0;
                }
                if (wantedLeft == actualLeft && wantedRight == actualRight) {
                    int count = wantedRight - wantedLeft + 1;
                    return sum[i] + count * lazy[i];
                }
                if (lazy[i] != 0) propagate(i, actualLeft, actualRight);
                int d = (actualRight - actualLeft + 1) / 2;
                long left = querySum(wantedLeft, min(actualLeft+d-1, wantedRight), 2*i, actualLeft, actualLeft+d-1);
                long right = querySum(max(actualLeft+d, wantedLeft), wantedRight, 2*i+1, actualLeft+d, actualRight);
                return left + right;
            }

            private long queryMin(int wantedLeft, int wantedRight, int i, int actualLeft, int actualRight) {
                if (wantedLeft > actualRight || wantedRight < actualLeft) {
                    return 0;
                }
                if (wantedLeft == actualLeft && wantedRight == actualRight) {
                    return min[i] + lazy[i];
                }
                if (lazy[i] != 0) propagate(i, actualLeft, actualRight);
                int d = (actualRight - actualLeft + 1) / 2;
                long left = queryMin(wantedLeft, min(actualLeft+d-1, wantedRight), 2*i, actualLeft, actualLeft+d-1);
                long right = queryMin(max(actualLeft+d, wantedLeft), wantedRight, 2*i+1, actualLeft+d, actualRight);
                return min(left, right);
            }

            private long queryMax(int wantedLeft, int wantedRight, int i, int actualLeft, int actualRight) {
                if (wantedLeft > actualRight || wantedRight < actualLeft) {
                    return 0;
                }
                if (wantedLeft == actualLeft && wantedRight == actualRight) {
                    return max[i] + lazy[i];
                }
                if (lazy[i] != 0) propagate(i, actualLeft, actualRight);
                int d = (actualRight - actualLeft + 1) / 2;
                long left = queryMax(wantedLeft, min(actualLeft+d-1, wantedRight), 2*i, actualLeft, actualLeft+d-1);
                long right = queryMax(max(actualLeft+d, wantedLeft), wantedRight, 2*i+1, actualLeft+d, actualRight);
                return max(left, right);
            }

            private void modifyRec(int wantedLeft, int wantedRight, int i, int actualLeft, int actualRight, long value) {
                if (wantedLeft > actualRight || wantedRight < actualLeft) {
                    return;
                }
                if (wantedLeft == actualLeft && wantedRight == actualRight) {
                    lazy[i] += value;
                    return;
                }
                if (lazy[i] != 0) propagate(i, actualLeft, actualRight);
                int d = (actualRight - actualLeft + 1) / 2;
                modifyRec(wantedLeft, min(actualLeft+d-1, wantedRight), 2*i, actualLeft, actualLeft+d-1, value);
                modifyRec(max(actualLeft+d, wantedLeft), wantedRight, 2*i+1, actualLeft+d, actualRight, value);
                if (supportSum) sum[i] += value * (min(actualRight, wantedRight) - max(actualLeft, wantedLeft) + 1);
                if (supportMin) min[i] = min(min[2*i] + lazy[2*i], min[2*i+1] + lazy[2*i+1]);
                if (supportMax) max[i] = max(max[2*i] + lazy[2*i], max[2*i+1] + lazy[2*i+1]);
            }

            private void propagate(int i, int actualLeft, int actualRight) {
                lazy[2*i] += lazy[i];
                lazy[2*i+1] += lazy[i];
                if (supportSum) sum[i] += lazy[i] * (actualRight - actualLeft + 1);
                if (supportMin) min[i] += lazy[i];
                if (supportMax) max[i] += lazy[i];
                lazy[i] = 0;
            }

        }

        /***************************** Graphs *****************************/

        List<Integer>[] toGraph(IO io, int n) {
            List<Integer>[] g = new ArrayList[n+1];
            for (int i=1; i<=n; i++) g[i] = new ArrayList<>();
            for (int i=1; i<=n-1; i++) {
                int a = io.nextInt();
                int b = io.nextInt();
                g[a].add(b);
                g[b].add(a);
            }
            return g;
        }

        class Graph {
            HashMap<Long, List<Long>> edges;

            public Graph() {
                edges = new HashMap<>();
            }

            List<Long> getSetNeighbors(Long node) {
                List<Long> neighbors = edges.get(node);
                if (neighbors == null) {
                    neighbors = new ArrayList<>();
                    edges.put(node, neighbors);
                }
                return neighbors;
            }

            void addBiEdge(Long a, Long b) {
                addEdge(a, b);
                addEdge(b, a);
            }

            void addEdge(Long from, Long to) {
                getSetNeighbors(to); // make sure all have initialized lists
                List<Long> neighbors = getSetNeighbors(from);
                neighbors.add(to);
            }

            // topoSort variables
            int UNTOUCHED = 0;
            int FINISHED = 2;
            int INPROGRESS = 1;
            HashMap<Long, Integer> vis;
            List<Long> topoAns;
            List<Long> failDueToCycle = new ArrayList<Long>() {{ add(-1L); }};

            List<Long> topoSort() {
                topoAns = new ArrayList<>();
                vis = new HashMap<>();
                for (Long a : edges.keySet()) {
                    if (!topoDFS(a)) return failDueToCycle;
                }
                Collections.reverse(topoAns);
                return topoAns;
            }

            boolean topoDFS(long curr) {
                Integer status = vis.get(curr);
                if (status == null) status = UNTOUCHED;
                if (status == FINISHED) return true;
                if (status == INPROGRESS) {
                    return false;
                }
                vis.put(curr, INPROGRESS);
                for (long next : edges.get(curr)) {
                    if (!topoDFS(next)) return false;
                }
                vis.put(curr, FINISHED);
                topoAns.add(curr);
                return true;
            }

        }

        public class StronglyConnectedComponents {

            /** Kosaraju's algorithm */

            ArrayList<Integer>[] forw;
            ArrayList<Integer>[] bacw;

            /** Use: getCount(2, new int[] {1,2}, new int[] {2,1}) */
            public int getCount(int n, int[] mista, int[] minne) {
                forw = new ArrayList[n+1];
                bacw = new ArrayList[n+1];
                for (int i=1; i<=n; i++) {
                    forw[i] = new ArrayList<Integer>();
                    bacw[i] = new ArrayList<Integer>();
                }
                for (int i=0; i<mista.length; i++) {
                    int a = mista[i];
                    int b = minne[i];
                    forw[a].add(b);
                    bacw[b].add(a);
                }
                int count = 0;
                List<Integer> list = new ArrayList<Integer>();
                boolean[] visited = new boolean[n+1];
                for (int i=1; i<=n; i++) {
                    dfsForward(i, visited, list);
                }
                visited = new boolean[n+1];
                for (int i=n-1; i>=0; i--) {
                    int node = list.get(i);
                    if (visited[node]) continue;
                    count++;
                    dfsBackward(node, visited);
                }
                return count;
            }

            public void dfsForward(int i, boolean[] visited, List<Integer> list) {
                if (visited[i]) return;
                visited[i] = true;
                for (int neighbor : forw[i]) {
                    dfsForward(neighbor, visited, list);
                }
                list.add(i);
            }

            public void dfsBackward(int i, boolean[] visited) {
                if (visited[i]) return;
                visited[i] = true;
                for (int neighbor : bacw[i]) {
                    dfsBackward(neighbor, visited);
                }
            }
        }

        class LCAFinder {

            /* O(n log n) Initialize: new LCAFinder(graph)
             * O(log n) Queries: find(a,b) returns lowest common ancestor for nodes a and b */

            int[] nodes;
            int[] depths;
            int[] entries;
            int pointer;
            FenwickMin fenwick;

            public LCAFinder(List<Integer>[] graph) {
                this.nodes = new int[(int)10e6];
                this.depths = new int[(int)10e6];
                this.entries = new int[graph.length];
                this.pointer = 1;
                boolean[] visited = new boolean[graph.length+1];
                dfs(1, 0, graph, visited);
                fenwick = new FenwickMin(pointer-1);
                for (int i=1; i<pointer; i++) {
                    fenwick.set(i, depths[i] * 1000000L + i);
                }
            }

            private void dfs(int node, int depth, List<Integer>[] graph, boolean[] visited) {
                visited[node] = true;
                entries[node] = pointer;
                nodes[pointer] = node;
                depths[pointer] = depth;
                pointer++;
                for (int neighbor : graph[node]) {
                    if (visited[neighbor]) continue;
                    dfs(neighbor, depth+1, graph, visited);
                    nodes[pointer] = node;
                    depths[pointer] = depth;
                    pointer++;
                }
            }

            public int find(int a, int b) {
                int left = entries[a];
                int right = entries[b];
                if (left > right) {
                    int temp = left;
                    left = right;
                    right = temp;
                }
                long mixedBag = fenwick.getMin(left, right);
                int index = (int) (mixedBag % 1000000L);
                return nodes[index];
            }
        }

        /**************************** Geometry ****************************/

        class Point {
            int y;
            int x;

            public Point(int y, int x) {
                this.y = y;
                this.x = x;
            }
        }

        boolean segmentsIntersect(double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4) {
            // Returns true if segment 1-2 intersects segment 3-4

            if (x1 == x2 && x3 == x4) {
                // Both segments are vertical
                if (x1 != x3) return false;
                if (min(y1,y2) < min(y3,y4)) {
                    return max(y1,y2) >= min(y3,y4);
                } else {
                    return max(y3,y4) >= min(y1,y2);
                }
            }
            if (x1 == x2) {
                // Only segment 1-2 is vertical. Does segment 3-4 cross it? y = a*x + b
                double a34 = (y4-y3)/(x4-x3);
                double b34 = y3 - a34*x3;
                double y = a34 * x1 + b34;
                return y >= min(y1,y2) && y <= max(y1,y2) && x1 >= min(x3,x4) && x1 <= max(x3,x4);
            }
            if (x3 == x4) {
                // Only segment 3-4 is vertical. Does segment 1-2 cross it? y = a*x + b
                double a12 = (y2-y1)/(x2-x1);
                double b12 = y1 - a12*x1;
                double y = a12 * x3 + b12;
                return y >= min(y3,y4) && y <= max(y3,y4) && x3 >= min(x1,x2) && x3 <= max(x1,x2);
            }
            double a12 = (y2-y1)/(x2-x1);
            double b12 = y1 - a12*x1;
            double a34 = (y4-y3)/(x4-x3);
            double b34 = y3 - a34*x3;
            if (closeToZero(a12 - a34)) {
                // Parallel lines
                return closeToZero(b12 - b34);
            }
            // Non parallel non vertical lines intersect at x. Is x part of both segments?
            double x = -(b12-b34)/(a12-a34);
            return x >= min(x1,x2) && x <= max(x1,x2) && x >= min(x3,x4) && x <= max(x3,x4);
        }

        boolean pointInsideRectangle(Point p, List<Point> r, boolean countBorderAsInside) {
            Point a = r.get(0);
            Point b = r.get(1);
            Point c = r.get(2);
            Point d = r.get(3);
            double apd = areaOfTriangle(a, p, d);
            double dpc = areaOfTriangle(d, p, c);
            double cpb = areaOfTriangle(c, p, b);
            double pba = areaOfTriangle(p, b, a);
            double sumOfAreas = apd + dpc + cpb + pba;
            if (closeToZero(sumOfAreas - areaOfRectangle(r))) {
                if (closeToZero(apd) || closeToZero(dpc) || closeToZero(cpb) || closeToZero(pba)) {
                    return countBorderAsInside;
                }
                return true;
            }
            return false;
        }

        double areaOfTriangle(Point a, Point b, Point c) {
            return 0.5 * Math.abs((a.x-c.x)*(b.y-a.y)-(a.x-b.x)*(c.y-a.y));
        }

        double areaOfRectangle(List<Point> r) {
            double side1xDiff = r.get(0).x - r.get(1).x;
            double side1yDiff = r.get(0).y - r.get(1).y;
            double side2xDiff = r.get(1).x - r.get(2).x;
            double side2yDiff = r.get(1).y - r.get(2).y;
            double side1 = Math.sqrt(side1xDiff * side1xDiff + side1yDiff * side1yDiff);
            double side2 = Math.sqrt(side2xDiff * side2xDiff + side2yDiff * side2yDiff);
            return side1 * side2;
        }

        boolean pointsOnSameLine(double x1, double y1, double x2, double y2, double x3, double y3) {
            double areaTimes2 = x1 * (y2 - y3) + x2 * (y3 - y1) + x3 * (y1 - y2);
            return (closeToZero(areaTimes2));
        }

        class PointToLineSegmentDistanceCalculator {

            // Just call this
            double minDistFromPointToLineSegment(double point_x, double point_y, double x1, double y1, double x2, double y2) {
                return Math.sqrt(distToSegmentSquared(point_x, point_y, x1, y1, x2, y2));
            }

            private double distToSegmentSquared(double point_x, double point_y, double x1, double y1, double x2, double y2) {
                double l2 = dist2(x1,y1,x2,y2);
                if (l2 == 0) return dist2(point_x, point_y, x1, y1);
                double t = ((point_x - x1) * (x2 - x1) + (point_y - y1) * (y2 - y1)) / l2;
                if (t < 0) return dist2(point_x, point_y, x1, y1);
                if (t > 1) return dist2(point_x, point_y, x2, y2);

                double com_x = x1 + t * (x2 - x1);
                double com_y = y1 + t * (y2 - y1);
                return dist2(point_x, point_y, com_x, com_y);
            }

            private double dist2(double x1, double y1, double x2, double y2) {
                return Math.pow((x1 - x2), 2) + Math.pow((y1 - y2), 2);
            }

        }

        /****************************** Math ******************************/

        long pow(long base, int exp) {
            if (exp == 0) return 1L;
            long x = pow(base, exp/2);
            long ans = x * x;
            if (exp % 2 != 0) ans *= base;
            return ans;
        }

        long gcd(long... v) {
            /** Chained calls to Euclidean algorithm. */
            if (v.length == 1) return v[0];
            long ans = gcd(v[1], v[0]);
            for (int i=2; i<v.length; i++) {
                ans = gcd(ans, v[i]);
            }
            return ans;
        }

        long gcd(long a, long b) {
            /** Euclidean algorithm. */
            if (b == 0) return a;
            return gcd(b, a%b);
        }

        int[] generatePrimesUpTo(int last) {
            /* Sieve of Eratosthenes. Practically O(n). Values of 0 indicate primes. */
            int[] div = new int[last+1];
            for (int x=2; x<=last; x++) {
                if (div[x] > 0) continue;
                for (int u=2*x; u<=last; u+=x) {
                    div[u] = x;
                }
            }
            return div;
        }

        long lcm(long a, long b) {
            /** Least common multiple */
            return a * b / gcd(a,b);
        }

        class BaseConverter {

            /* Palauttaa luvun esityksen kannassa base */
            public String convert(Long number, int base) {
                return Long.toString(number, base);
            }

            /* Palauttaa luvun esityksen kannassa baseTo, kun annetaan luku Stringinä kannassa baseFrom */
            public String convert(String number, int baseFrom, int baseTo) {
                return Long.toString(Long.parseLong(number, baseFrom), baseTo);
            }

            /* Tulkitsee kannassa base esitetyn luvun longiksi (kannassa 10) */
            public long longify(String number, int baseFrom) {
                return Long.parseLong(number, baseFrom);
            }
        }

        class BinomialCoefficients {
            /** Total number of K sized unique combinations from pool of size N (unordered)
             N! / ( K! (N - K)! )   */

            /** For simple queries where output fits in long. */
            public long biCo(long n, long k) {
                long r = 1;
                if (k > n) return 0;
                for (long d = 1; d <= k; d++) {
                    r *= n--;
                    r /= d;
                }
                return r;
            }

            /** For multiple queries with same n, different k. */
            public long[] precalcBinomialCoefficientsK(int n, int maxK) {
                long v[] = new long[maxK+1];
                v[0] = 1; // nC0 == 1
                for (int i=1; i<=n; i++) {
                    for (int j=Math.min(i,maxK); j>0; j--) {
                        v[j] = v[j] + v[j-1]; // Pascal's triangle
                    }
                }
                return v;
            }

            /** When output needs % MOD. */
            public long[] precalcBinomialCoefficientsK(int n, int k, long M) {
                long v[] = new long[k+1];
                v[0] = 1; // nC0 == 1
                for (int i=1; i<=n; i++) {
                    for (int j=Math.min(i,k); j>0; j--) {
                        v[j] = v[j] + v[j-1]; // Pascal's triangle
                        v[j] %= M;
                    }
                }
                return v;
            }
        }

        /**************************** Strings ****************************/

        class Zalgo {

            public int pisinEsiintyma(String haku, String kohde) {
                char[] s = new char[haku.length() + 1 + kohde.length()];
                for (int i=0; i<haku.length(); i++) {
                    s[i] = haku.charAt(i);
                }
                int j = haku.length();
                s[j++] = '#';
                for (int i=0; i<kohde.length(); i++) {
                    s[j++] = kohde.charAt(i);
                }
                int[] z = toZarray(s);
                int max = 0;
                for (int i=haku.length(); i<z.length; i++) {
                    max = Math.max(max, z[i]);
                }
                return max;
            }

            public int[] toZarray(char[] s) {
                int n = s.length;
                int[] z = new int[n];
                int a = 0, b = 0;
                for (int i = 1; i < n; i++) {
                    if (i > b) {
                        for (int j = i; j < n && s[j - i] == s[j]; j++)  z[i]++;
                    }
                    else {
                        z[i] = z[i - a];
                        if (i + z[i - a] > b) {
                            for (int j = b + 1; j < n && s[j - i] == s[j]; j++) z[i]++;
                            a = i;
                            b = i + z[i] - 1;
                        }
                    }
                }
                return z;
            }

            public List<Integer> getStartIndexesWhereWordIsFound(String haku, String kohde) {
                // this is alternative use case
                char[] s = new char[haku.length() + 1 + kohde.length()];
                for (int i=0; i<haku.length(); i++) {
                    s[i] = haku.charAt(i);
                }
                int j = haku.length();
                s[j++] = '#';
                for (int i=0; i<kohde.length(); i++) {
                    s[j++] = kohde.charAt(i);
                }
                int[] z = toZarray(s);
                List<Integer> indexes = new ArrayList<>();
                for (int i=haku.length(); i<z.length; i++) {
                    if (z[i] < haku.length()) continue;
                    indexes.add(i);
                }
                return indexes;
            }

        }

        class StringHasher {

            class HashedString {
                long[] hashes;
                long[] modifiers;

                public HashedString(long[] hashes, long[] modifiers) {
                    this.hashes = hashes;
                    this.modifiers = modifiers;
                }
            }

            long P;
            long M;

            public StringHasher() {
                initializePandM();
            }

            HashedString hashString(String s) {
                int n = s.length();
                long[] hashes = new long[n];
                long[] modifiers = new long[n];
                hashes[0] = s.charAt(0);
                modifiers[0] = 1;
                for (int i=1; i<n; i++) {
                    hashes[i] = (hashes[i-1] * P + s.charAt(i)) % M;
                    modifiers[i] = (modifiers[i-1] * P) % M;
                }
                return new HashedString(hashes, modifiers);
            }

            /**
             * Indices are inclusive.
             */
            long getHash(HashedString hashedString, int startIndex, int endIndex) {
                long[] hashes = hashedString.hashes;
                long[] modifiers = hashedString.modifiers;
                long result = hashes[endIndex];
                if (startIndex > 0) result -= (hashes[startIndex-1] * modifiers[endIndex-startIndex+1]) % M;
                if (result < 0) result += M;
                return result;
            }

            // Less interesting methods below

            /**
             * Efficient for 2 input parameter strings in particular.
             */
            HashedString[] hashString(String first, String second) {
                HashedString[] array = new HashedString[2];
                int n = first.length();
                long[] modifiers = new long[n];
                modifiers[0] = 1;

                long[] firstHashes = new long[n];
                firstHashes[0] = first.charAt(0);
                array[0] = new HashedString(firstHashes, modifiers);

                long[] secondHashes = new long[n];
                secondHashes[0] = second.charAt(0);
                array[1] = new HashedString(secondHashes, modifiers);

                for (int i=1; i<n; i++) {
                    modifiers[i] = (modifiers[i-1] * P) % M;
                    firstHashes[i] = (firstHashes[i-1] * P + first.charAt(i)) % M;
                    secondHashes[i] = (secondHashes[i-1] * P + second.charAt(i)) % M;
                }
                return array;
            }

            /**
             * Efficient for 3+ strings
             * More efficient than multiple hashString calls IF strings are same length.
             */
            HashedString[] hashString(String... strings) {
                HashedString[] array = new HashedString[strings.length];
                int n = strings[0].length();
                long[] modifiers = new long[n];
                modifiers[0] = 1;
                for (int j=0; j<strings.length; j++) {
                    // if all strings are not same length, defer work to another method
                    if (strings[j].length() != n) {
                        for (int i=0; i<n; i++) {
                            array[i] = hashString(strings[i]);
                        }
                        return array;
                    }

                    // otherwise initialize stuff
                    long[] hashes = new long[n];
                    hashes[0] = strings[j].charAt(0);
                    array[j] = new HashedString(hashes, modifiers);
                }
                for (int i=1; i<n; i++) {
                    modifiers[i] = (modifiers[i-1] * P) % M;
                    for (int j=0; j<strings.length; j++) {
                        String s = strings[j];
                        long[] hashes = array[j].hashes;
                        hashes[i] = (hashes[i-1] * P + s.charAt(i)) % M;
                    }
                }
                return array;
            }

            void initializePandM() {
                ArrayList<Long> modOptions = new ArrayList<>(20);
                modOptions.add(353873237L);
                modOptions.add(353875897L);
                modOptions.add(353878703L);
                modOptions.add(353882671L);
                modOptions.add(353885303L);
                modOptions.add(353888377L);
                modOptions.add(353893457L);
                P = modOptions.get(new Random().nextInt(modOptions.size()));

                modOptions.clear();
                modOptions.add(452940277L);
                modOptions.add(452947687L);
                modOptions.add(464478431L);
                modOptions.add(468098221L);
                modOptions.add(470374601L);
                modOptions.add(472879717L);
                modOptions.add(472881973L);
                M = modOptions.get(new Random().nextInt(modOptions.size()));

            }
        }

        int editDistance(String a, String b) {
            a = "#"+a;
            b = "#"+b;
            int n = a.length();
            int m = b.length();
            int[][] dp = new int[n+1][m+1];
            for (int y=0; y<=n; y++) {
                for (int x=0; x<=m; x++) {
                    if (y == 0) dp[y][x] = x;
                    else if (x == 0) dp[y][x] = y;
                    else {
                        int e1 = dp[y-1][x] + 1;
                        int e2 = dp[y][x-1] + 1;
                        int e3 = dp[y-1][x-1] + (a.charAt(y-1) != b.charAt(x-1) ? 1 : 0);
                        dp[y][x] = min(e1, e2, e3);
                    }
                }
            }
            return dp[n][m];
        }

        /*************************** Technical ***************************/

        private class IO extends PrintWriter {
            private InputStreamReader r;
            private static final int BUFSIZE = 1 << 15;
            private char[] buf;
            private int bufc;
            private int bufi;
            private StringBuilder sb;

            public IO() {
                super(new BufferedOutputStream(System.out));
                r = new InputStreamReader(System.in);

                buf = new char[BUFSIZE];
                bufc = 0;
                bufi = 0;
                sb = new StringBuilder();
            }

            /** Print, flush, return nextInt. */
            private int queryInt(String s) {
                io.println(s);
                io.flush();
                return nextInt();
            }

            /** Print, flush, return nextLong. */
            private long queryLong(String s) {
                io.println(s);
                io.flush();
                return nextLong();
            }

            /** Print, flush, return next word. */
            private String queryNext(String s) {
                io.println(s);
                io.flush();
                return next();
            }

            private void fillBuf() throws IOException {
                bufi = 0;
                bufc = 0;
                while(bufc == 0) {
                    bufc = r.read(buf, 0, BUFSIZE);
                    if(bufc == -1) {
                        bufc = 0;
                        return;
                    }
                }
            }

            private boolean pumpBuf() throws IOException {
                if(bufi == bufc) {
                    fillBuf();
                }
                return bufc != 0;
            }

            private boolean isDelimiter(char c) {
                return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f';
            }

            private void eatDelimiters() throws IOException {
                while(true) {
                    if(bufi == bufc) {
                        fillBuf();
                        if(bufc == 0) throw new RuntimeException("IO: Out of input.");
                    }

                    if(!isDelimiter(buf[bufi])) break;
                    ++bufi;
                }
            }

            public String next() {
                try {
                    sb.setLength(0);

                    eatDelimiters();
                    int start = bufi;

                    while(true) {
                        if(bufi == bufc) {
                            sb.append(buf, start, bufi - start);
                            fillBuf();
                            start = 0;
                            if(bufc == 0) break;
                        }

                        if(isDelimiter(buf[bufi])) break;
                        ++bufi;
                    }

                    sb.append(buf, start, bufi - start);

                    return sb.toString();
                } catch(IOException e) {
                    throw new RuntimeException("IO.next: Caught IOException.");
                }
            }

            public int nextInt() {
                try {
                    int ret = 0;

                    eatDelimiters();

                    boolean positive = true;
                    if(buf[bufi] == '-') {
                        ++bufi;
                        if(!pumpBuf()) throw new RuntimeException("IO.nextInt: Invalid int.");
                        positive = false;
                    }

                    boolean first = true;
                    while(true) {
                        if(!pumpBuf()) break;
                        if(isDelimiter(buf[bufi])) {
                            if(first) throw new RuntimeException("IO.nextInt: Invalid int.");
                            break;
                        }
                        first = false;

                        if(buf[bufi] >= '0' && buf[bufi] <= '9') {
                            if(ret < -214748364) throw new RuntimeException("IO.nextInt: Invalid int.");
                            ret *= 10;
                            ret -= (int)(buf[bufi] - '0');
                            if(ret > 0) throw new RuntimeException("IO.nextInt: Invalid int.");
                        } else {
                            throw new RuntimeException("IO.nextInt: Invalid int.");
                        }

                        ++bufi;
                    }

                    if(positive) {
                        if(ret == -2147483648) throw new RuntimeException("IO.nextInt: Invalid int.");
                        ret = -ret;
                    }

                    return ret;
                } catch(IOException e) {
                    throw new RuntimeException("IO.nextInt: Caught IOException.");
                }
            }

            public long nextLong() {
                try {
                    long ret = 0;

                    eatDelimiters();

                    boolean positive = true;
                    if(buf[bufi] == '-') {
                        ++bufi;
                        if(!pumpBuf()) throw new RuntimeException("IO.nextLong: Invalid long.");
                        positive = false;
                    }

                    boolean first = true;
                    while(true) {
                        if(!pumpBuf()) break;
                        if(isDelimiter(buf[bufi])) {
                            if(first) throw new RuntimeException("IO.nextLong: Invalid long.");
                            break;
                        }
                        first = false;

                        if(buf[bufi] >= '0' && buf[bufi] <= '9') {
                            if(ret < -922337203685477580L) throw new RuntimeException("IO.nextLong: Invalid long.");
                            ret *= 10;
                            ret -= (long)(buf[bufi] - '0');
                            if(ret > 0) throw new RuntimeException("IO.nextLong: Invalid long.");
                        } else {
                            throw new RuntimeException("IO.nextLong: Invalid long.");
                        }

                        ++bufi;
                    }

                    if(positive) {
                        if(ret == -9223372036854775808L) throw new RuntimeException("IO.nextLong: Invalid long.");
                        ret = -ret;
                    }

                    return ret;
                } catch(IOException e) {
                    throw new RuntimeException("IO.nextLong: Caught IOException.");
                }
            }

            public double nextDouble() {
                return Double.parseDouble(next());
            }

        }

        void print(Object output) {
            io.println(output);
        }

        void done(Object output) {
            print(output);
            done();
        }

        void done() {
            io.close();
            throw new RuntimeException("Clean exit");
        }

        long min(long... v) {
            long ans = v[0];
            for (int i=1; i<v.length; i++) {
                ans = Math.min(ans, v[i]);
            }
            return ans;
        }

        double min(double... v) {
            double ans = v[0];
            for (int i=1; i<v.length; i++) {
                ans = Math.min(ans, v[i]);
            }
            return ans;
        }

        int min(int... v) {
            int ans = v[0];
            for (int i=1; i<v.length; i++) {
                ans = Math.min(ans, v[i]);
            }
            return ans;
        }

        long max(long... v) {
            long ans = v[0];
            for (int i=1; i<v.length; i++) {
                ans = Math.max(ans, v[i]);
            }
            return ans;
        }

        double max(double... v) {
            double ans = v[0];
            for (int i=1; i<v.length; i++) {
                ans = Math.max(ans, v[i]);
            }
            return ans;
        }

        int max(int... v) {
            int ans = v[0];
            for (int i=1; i<v.length; i++) {
                ans = Math.max(ans, v[i]);
            }
            return ans;
        }

    }

}

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.util.StringTokenizer;

/**
 * Created by timur on 28.03.15.
 */

public class TaskD {
    boolean eof;
    BufferedReader br;
    StringTokenizer st;
    PrintWriter out;

    public static void main(String[] args) throws IOException {
        new TaskD().run();
    }

    public String nextToken() {
        while (st == null || !st.hasMoreTokens()) {
            try {
                st = new StringTokenizer(br.readLine());
            } catch (Exception e) {
                eof = true;
                return "-1";
            }
        }
        return st.nextToken();
    }

    public int nextInt() {
        return Integer.parseInt(nextToken());
    }

    public long nextLong() {
        return Long.parseLong(nextToken());
    }

    double nextDouble() {
        return Double.parseDouble(nextToken());
    }

    String nextLine() throws IOException {
        return br.readLine();
    }

    void run() throws IOException {
        InputStream input = System.in;
        PrintStream output = System.out;
        try {
            File f = new File("a.in");
            if (f.exists() && f.canRead()) {
                input = new FileInputStream(f);
                output = new PrintStream("a.out");
            }
        } catch (Throwable e) {
        }
        br = new BufferedReader(new InputStreamReader(input));
        out = new PrintWriter(output);
        solve();
        br.close();
        out.close();
    }

    long gcd(long a, long b) {
        if (b == 0)
            return a;
        else
            return gcd(b, a % b);
    }

    class Pair implements Comparable<Pair> {
        int move, change, num;

        public Pair(int move, int change, int num) {
            this.move = move;
            this.change = change;
            this.num = num;

        }

        @Override
        public int compareTo(Pair o) {
            int r = move - o.move;
            if (r == 0)
                r = this.change - o.change;
            return r;
        }

        public Pair up() {
            return new Pair(move + 1, change + 1, num);
        }
    }

    Pair min(Pair a, Pair b) {
        if (a == null)
            return b;
        if (b == null)
            return a;
        if (b.compareTo(a) < 0)
            return b;
        return a;
    }

    Pair numin(Pair a, Pair b) {
        if (a == null)
            return b;
        if (b == null)
            return a;
        if (a.num > b.num)
            return a;
        if (a.num < b.num)
            return b;
        return min(a, b);
    }

    class map {
        Pair[][] m;

        public map() {
            m = new Pair[n][n];
        }
    }

    int n, k;

    void solve() {
        n = nextInt();
        k = n * n;
        int[] x = new int[k + 1];
        int[] y = new int[k + 1];
        int u;
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                u = nextInt() - 1;
                x[u] = i;
                y[u] = j;
            }
        }
        int inf = k * 2 + 5;
        map[] h = new map[inf];
        map[] s = new map[inf];
        map[] l = new map[inf];
        for (int i = 0; i < inf; i++) {
            h[i] = new map();
            s[i] = new map();
            l[i] = new map();
        }
        h[0].m[x[0]][y[0]] = new Pair(0, 0, 1);
        l[0].m[x[0]][y[0]] = new Pair(0, 0, 1);
        s[0].m[x[0]][y[0]] = new Pair(0, 0, 1);

        int[] dxh = {-2, -2, -1, 1, 2, 2, 1, -1};
        int[] dyh = {-1, 1, 2, 2, 1, -1, -2, -2};

        int[] dxs = {-1, -1, 1, 1};
        int[] dys = {-1, 1, 1, -1};

        int[] dxl = {-1, 0, 1, 0};
        int[] dyl = {0, 1, 0, -1};

        int nx, ny;
        Pair np;
        Pair cp;

        Pair ans = null;
        for (int i = 0; i < inf - 3; i++) {
            for (int xx = 0; xx < n; xx++) {
                for (int yy = 0; yy < n; yy++) {
                    cp = h[i].m[xx][yy];
                    if (cp != null) {
                        for (int j = 0; j < 8; j++) {
                            nx = xx + dxh[j];
                            ny = yy + dyh[j];
                            if (nx >= 0 && ny >= 0 && nx < n && ny < n) {
                                np = new Pair(cp.move + 1, cp.change, cp.num);
                                if (cp.num < k && nx == x[cp.num] && ny == y[cp.num]) {
                                    np = new Pair(cp.move + 1, cp.change, cp.num + 1);
                                }
                                h[i + 1].m[nx][ny] = numin(h[i + 1].m[nx][ny], np);
                                s[i + 2].m[nx][ny] = numin(s[i + 2].m[nx][ny], np.up());
                                l[i + 2].m[nx][ny] = numin(l[i + 2].m[nx][ny], np.up());
                                ans = numin(ans, np);
                            }
                        }
                    }
                    cp = s[i].m[xx][yy];
                    if (cp != null) {
                        for (int j = 0; j < 4; j++) {
                            for (int mul = 1; mul < n + 1; mul++) {
                                nx = xx + dxs[j] * mul;
                                ny = yy + dys[j] * mul;
                                if (nx >= 0 && ny >= 0 && nx < n && ny < n) {
                                    np = new Pair(cp.move + 1, cp.change, cp.num);
                                    if (cp.num < k && nx == x[cp.num] && ny == y[cp.num]) {
                                        np = new Pair(cp.move + 1, cp.change, cp.num + 1);
                                    }
                                    s[i + 1].m[nx][ny] = numin(s[i + 1].m[nx][ny], np);
                                    h[i + 2].m[nx][ny] = numin(h[i + 2].m[nx][ny], np.up());
                                    l[i + 2].m[nx][ny] = numin(l[i + 2].m[nx][ny], np.up());
                                    ans = numin(ans, np);
                                } else {
                                    break;
                                }
                            }
                        }
                    }
                    cp = l[i].m[xx][yy];
                    if (cp != null) {
                        for (int j = 0; j < 4; j++) {
                            for (int mul = 1; mul < n + 1; mul++) {
                                nx = xx + dxl[j] * mul;
                                ny = yy + dyl[j] * mul;
                                if (nx >= 0 && ny >= 0 && nx < n && ny < n) {
                                    np = new Pair(cp.move + 1, cp.change, cp.num);
                                    if (cp.num < k && nx == x[cp.num] && ny == y[cp.num]) {
                                        np = new Pair(cp.move + 1, cp.change, cp.num + 1);
                                    }
                                    l[i + 1].m[nx][ny] = numin(l[i + 1].m[nx][ny], np);
                                    s[i + 2].m[nx][ny] = numin(s[i + 2].m[nx][ny], np.up());
                                    h[i + 2].m[nx][ny] = numin(h[i + 2].m[nx][ny], np.up());
                                    ans = numin(ans, np);
                                } else {
                                    break;
                                }
                            }
                        }
                    }
                }
            }
        }

        out.print(ans.move + " " + ans.change);
        /*

        for (int i = 1; i < k; i++) {
            if (x[i] == x[i - 1] || y[i] == y[i - 1]) {
                l[i] = l[i - 1] + 1;
            } else {
                l[i] = l[i - 1] + 2;
            }
            zl[i] = zl[i - 1];
            s[i] = l[i] + 1;
            zs[i] = zl[i - 1] + 1;
            h[i] = l[i] + 1;
            zh[i] = zl[i - 1] + 1;
            if ((x[i] + y[i]) % 2 == (x[i - 1] + y[i - 1]) % 2) {
                if ((x[i] - y[i] == x[i - 1] - y[i - 1]) ||
                    (x[i] + y[i] == x[i - 1] + y[i - 1])) {
                    s[i] = s[i - 1] + 1;
                } else {
                    s[i] = s[i - 1] + 2;
                    if (s[i] + 1 < l[i] || (s[i] + 1 == l[i] && zs[i] + 1 < zl[i])) {
                        l[i] = s[i] + 1;
                        zl[i] = zs[i] + 1;
                    }
                    if (s[i] + 1 < h[i] || (s[i] + 1 == h[i] && zs[i] + 1 < zh[i])) {
                        h[i] = s[i] + 1;
                        zh[i] = zs[i] + 1;
                    }
                }
            }
            int mv = inf;
            Queue<Integer> xx = new ArrayDeque<>();
            Queue<Integer> yy = new ArrayDeque<>();
            Queue<Integer> c = new ArrayDeque<>();
            xx.add(x[i - 1]);
            yy.add(y[i - 1]);
            c.add(0);
            boolean[][] vis = new boolean[n][n];
            vis[x[i - 1]][y[i - 1]] = true;
            while (!xx.isEmpty()) {
                cx = xx.poll();
                cy = yy.poll();
                cc = c.poll();
                for (int j = 0; j < 8; j++) {
                    nx = cx + dx[j];
                    ny = cy + dy[j];
                    if (nx >= 0 && ny >= 0 && nx < n && ny < n) {
                        if (nx == x[i] && ny == y[i]) {
                            mv = cc + 1;
                            xx.clear();
                            break;
                        }
                        if (!vis[nx][ny]) {
                            xx.add(nx);
                            yy.add(ny);
                            c.add(cc + 1);
                            vis[nx][ny] = true;
                        }
                    }
                }
            }
            if (h[i - 1] + mv < h[i] || (h[i - 1] + mv == h[i] && zh[i] > zh[i - 1])) {
                h[i] = h[i - 1] + mv;
                zh[i] = zh[ i - 1];
            }
            if (h[i] + 1 < l[i] || (h[i] + 1 == l[i] && zh[i] + 1 < zl[i])) {
                l[i] = h[i] + 1;
                zl[i] = zh[i] + 1;
            }
            if (h[i] + 1 < s[i] || (h[i] + 1 == s[i] && zh[i] + 1 < zs[i])) {
                s[i] = h[i] + 1;
                zs[i] = zh[i] + 1;
            }
        }
        Pair lll = new Pair(l[k - 1], zl[k - 1]);
        Pair hhh = new Pair(h[k - 1], zh[k - 1]);
        Pair sss = new Pair(s[k - 1], zs[k - 1]);
        Pair res = lll;
        if (res.compareTo(hhh) > 0) {
            res = hhh;
        }
        if (res.compareTo(sss) > 0) {
            res = sss;
        }
        out.print(res.l + " " + res.change);
        */
    }

}

import java.util.*;
import java.lang.*;
import java.math.*;
import java.io.*;
import static java.lang.Math.*;

/* spar5h */

public class cf2{

	static int[][] dp, dp2, d, d2;
	static int[] ai, aj;

	static void update(int s_i, int f_i, int s_j, int f_j, int val, int c_i, int c_j, int rep) {

		for(int i = s_i; i <= f_i; i++) {

			for(int j = s_j; j <= f_j; j++) {

				int rpl = rep;

				if(i != c_i)
					rpl++;

				if(j != c_j)
					rpl++;

				if(d[i][j] > val + rpl) {
					d[i][j] = val + rpl; d2[i][j] = rpl;
				}
				else if(d[i][j] == val + rpl)
					d2[i][j] = min(rpl, d2[i][j]);

			}
		}
	}

	static boolean rook(int si, int sj, int ti, int tj) {

		if(si == ti || sj == tj)
			return true;

		return false;
	}

	static boolean bishop(int si, int sj, int ti, int tj) {

		if(abs(si - ti) == abs(sj - tj))
			return true;

		return false;
	}

	static boolean knight(int si, int sj, int ti, int tj) {

		if((abs(si - ti) == 1 && abs(sj - tj) == 2) || (abs(si - ti) == 2 && abs(sj - tj) == 1))
			return true;

		return false;
	}

	static void recur(int x, int n) {

		//rook: 0, bishop; 1, knight: 2

		if(x + 1 < n * n - 1)
			recur(x + 1, n);

		int si = ai[x], sj = aj[x], ti = ai[x + 1], tj = aj[x + 1];

		d = new int[3][3]; d2 = new int[3][3];

		for(int i = 0; i < 3; i++)
			for(int j = 0; j < 3; j++)
				d[i][j] = Integer.MAX_VALUE;

		update(0, 2, 0, 2, 2, 0, 0, 0);

		//1
		if(rook(si, sj, ti, tj))
			update(0, 2, 0, 2, 1, 0, 0, 0);

		if(bishop(si, sj, ti, tj))
			update(0, 2, 0, 2, 1, 1, 1, 0);

		if(knight(si, sj, ti, tj))
			update(0, 2, 0, 2, 1, 2, 2, 0);

		//2
		for(int xi = 0; xi < n; xi++) {

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

				if(bishop(si, sj, xi, xj) && bishop(xi, xj, ti, tj))
					update(1, 2, 1, 2, 2, 1, 1, 0);

				if(knight(si, sj, xi, xj) && knight(xi, xj, ti, tj))
					update(1, 2, 1, 2, 2, 2, 2, 0);

				if(bishop(si, sj, xi, xj) && knight(xi, xj, ti, tj))
					update(1, 1, 2, 2, 2, 1, 2, 1);

				if(knight(si, sj, xi, xj) && bishop(xi, xj, ti, tj))
					update(2, 2, 1, 1, 2, 2, 1, 1);
			}
		}

		//3
		for(int xi = 0; xi < n; xi++) {

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

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

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

						if(bishop(si, sj, xi, xj) && bishop(xi, xj, yi, yj) && bishop(yi, yj, ti, tj))
							update(1, 1, 1, 1, 3, 1, 1, 0);

						if(knight(si, sj, xi, xj) && knight(xi, xj, yi, yj) && knight(yi, yj, ti, tj)) {
							update(2, 2, 2, 2, 3, 2, 2, 0);
						}
					}
				}
			}
		}

		for(int i = 0; i < 3; i++)
			dp[x][i] = Integer.MAX_VALUE;

		for(int i = 0; i < 3; i++) {
			for(int j = 0; j < 3; j++) {

				if(dp[x][i] > d[i][j] + dp[x + 1][j]) {
					dp[x][i] = d[i][j] + dp[x + 1][j]; dp2[x][i] = d2[i][j] + dp2[x + 1][j];
				}
				else if(dp[x][i] == d[i][j] + dp[x + 1][j])
					dp2[x][i] = min(d2[i][j] + dp2[x + 1][j], dp2[x][i]);
			}
		}
	}

	public static void main(String[] args) {

		InputReader s = new InputReader(System.in);
		PrintWriter w = new PrintWriter(System.out);

		int n = s.nextInt();

		ai = new int[n * n];
		aj = new int[n * n];

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

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

				int x = s.nextInt() - 1;
				ai[x] = i; aj[x] = j;
			}
		}

		dp = new int[n * n][3];
		dp2 = new int[n * n][3];

		recur(0, n);

		int v1 = Integer.MAX_VALUE, v2 = 0;

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

			if(dp[0][i] < v1) {
				v1 = dp[0][i]; v2 = dp2[0][i];
			}
			else if(dp[0][i] == v1)
				v2 = min(dp2[0][i], v2);
		}

		w.println(v1 + " " + v2);

		w.close();
	}

	static class InputReader {

		private InputStream stream;
		private byte[] buf = new byte[1024];
		private int curChar;
		private int numChars;
		private SpaceCharFilter filter;

		public InputReader(InputStream stream)
		{
			this.stream = stream;
		}

		public int read()
		{
			if (numChars==-1)
				throw new InputMismatchException();

			if (curChar >= numChars)
			{
				curChar = 0;
				try
				{
					numChars = stream.read(buf);
				}
				catch (IOException e)
				{
					throw new InputMismatchException();
				}

				if(numChars <= 0)
					return -1;
			}
			return buf[curChar++];
		}

		public String nextLine()
		{
			BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
			String str = "";
            try
            {
                str = br.readLine();
            }
            catch (IOException e)
            {
                e.printStackTrace();
            }
            return str;
		}
		public int nextInt()
		{
			int c = read();

			while(isSpaceChar(c))
				c = read();

			int sgn = 1;

			if (c == '-')
			{
				sgn = -1;
				c = read();
			}

			int res = 0;
			do
			{
				if(c<'0'||c>'9')
					throw new InputMismatchException();
				res *= 10;
				res += c - '0';
				c = read();
			}
			while (!isSpaceChar(c));

			return res * sgn;
		}

		public long nextLong()
		{
			int c = read();
			while (isSpaceChar(c))
				c = read();
			int sgn = 1;
			if (c == '-')
			{
				sgn = -1;
				c = read();
			}
			long res = 0;

			do
			{
				if (c < '0' || c > '9')
					throw new InputMismatchException();
				res *= 10;
				res += c - '0';
				c = read();
			}
			while (!isSpaceChar(c));
				return res * sgn;
		}

		public double nextDouble()
		{
			int c = read();
			while (isSpaceChar(c))
				c = read();
			int sgn = 1;
			if (c == '-')
			{
				sgn = -1;
				c = read();
			}
			double res = 0;
			while (!isSpaceChar(c) && c != '.')
			{
				if (c == 'e' || c == 'E')
					return res * Math.pow(10, nextInt());
				if (c < '0' || c > '9')
					throw new InputMismatchException();
				res *= 10;
				res += c - '0';
				c = read();
			}
			if (c == '.')
			{
				c = read();
				double m = 1;
				while (!isSpaceChar(c))
				{
					if (c == 'e' || c == 'E')
						return res * Math.pow(10, nextInt());
					if (c < '0' || c > '9')
						throw new InputMismatchException();
					m /= 10;
					res += (c - '0') * m;
					c = read();
				}
			}
			return res * sgn;
		}

		public String readString()
		{
			int c = read();
			while (isSpaceChar(c))
				c = read();
			StringBuilder res = new StringBuilder();
			do
			{
				res.appendCodePoint(c);
				c = read();
			}
			while (!isSpaceChar(c));

			return res.toString();
		}

		public boolean isSpaceChar(int c)
		{
			if (filter != null)
				return filter.isSpaceChar(c);
			return c == ' ' || c == '\n' || c == '\r' || c == '\t' || c == -1;
		}

		public String next()
		{
			return readString();
		}

		public interface SpaceCharFilter
		{
			public boolean isSpaceChar(int ch);
		}
	}

}

import java.io.*;
import java.math.*;
import java.util.*;

public class ThreePieces {
    private static final int INF = 1234567890;

    public static void solve(FastIO io) {
        int N = io.nextInt();

        int r0 = -1, c0 = -1;
        int[][] A = new int[N][];
        for (int i = 0; i < N; ++i) {
            A[i] = io.nextIntArray(N);

            for (int j = 0; j < N; ++j) {
                if (A[i][j] == 1) {
                    r0 = i;
                    c0 = j;
                }
            }
        }

        LocList mb = new LocList();
        for (int i = 1; i <= N; ++i) {
            mb.add(new Loc(i, i));
            mb.add(new Loc(i, -i));
            mb.add(new Loc(-i, i));
            mb.add(new Loc(-i, -i));
        }

        LocList mk = new LocList();
        mk.add(new Loc(-2, 1));
        mk.add(new Loc(-1, 2));
        mk.add(new Loc(1, 2));
        mk.add(new Loc(2, 1));
        mk.add(new Loc(2, -1));
        mk.add(new Loc(1, -2));
        mk.add(new Loc(-1, -2));
        mk.add(new Loc(-2, -1));

        LocList mr = new LocList();
        for (int i = 1; i <= N; ++i) {
            mr.add(new Loc(0, i));
            mr.add(new Loc(0, -i));
            mr.add(new Loc(i, 0));
            mr.add(new Loc(-i, 0));
        }

        LocList[] moves = {mb, mk, mr};

        int N2 = N * N;
        int[][][][] dp = megaInit(N);
        dp[r0][c0][1][0] = dp[r0][c0][1][1] = dp[r0][c0][1][2] = 0;

        for (int t = 0;; ++t) {
            int[][][][] next = megaInit(N);
            int bestFinish = INF;
            for (int r = 0; r < N; ++r) {
                for (int c = 0; c < N; ++c) {
                    for (int i = 1; i < N2; ++i) {
                        for (int p = 0; p < 3; ++p) {
                            for (int np = 0; np < 3; ++np) {
                                next[r][c][i][np] = Math.min(next[r][c][i][np], 1 + dp[r][c][i][p]);
                            }
                            if (dp[r][c][i][p] < INF) {
                                for (Loc mv : moves[p]) {
                                    int nr = r + mv.R;
                                    int nc = c + mv.C;
                                    if (nr >= 0 && nr < N && nc >= 0 && nc < N) {
                                        int ni = i;
                                        if (A[nr][nc] == i + 1) {
                                            ++ni;
                                        }
                                        next[nr][nc][ni][p] = Math.min(next[nr][nc][ni][p], dp[r][c][i][p]);
                                    }
                                }
                            }
                        }
                    }
                    for (int p = 0; p < 3; ++p) {
                        bestFinish = Math.min(bestFinish, dp[r][c][N2][p]);
                    }
                }
            }
            if (bestFinish < INF) {
                io.printf("%d %d\n", t, bestFinish);
                return;
            }
            dp = next;
        }
    }

    private static int[][][][] megaInit(int N) {
        final int N2 = N * N;
        int[][][][] arr = new int[N][N][N2 + 1][3];
        for (int r = 0; r < N; ++r) {
            for (int c = 0; c < N; ++c) {
                for (int i = 0; i <= N2; ++i) {
                    Arrays.fill(arr[r][c][i], INF);
                }
            }
        }
        return arr;
    }

    private static class LocList extends ArrayList<Loc> {
    }

    private static class Loc {
        public int R, C;

        public Loc(int r, int c) {
            this.R = r;
            this.C = c;
        }
    }

    public static class FastIO {
        private InputStream reader;
        private PrintWriter writer;

        private byte[] buf = new byte[1024];
        private int curChar;
        private int numChars;

        public FastIO(InputStream r, OutputStream w) {
            reader = r;
            writer = new PrintWriter(new BufferedWriter(new OutputStreamWriter(w)));
        }

        public int read() {
            if (numChars == -1)
                throw new InputMismatchException();
            if (curChar >= numChars) {
                curChar = 0;
                try {
                    numChars = reader.read(buf);
                } catch (IOException e) {
                    throw new InputMismatchException();
                }
                if (numChars <= 0)
                    return -1;
            }
            return buf[curChar++];
        }

        public String nextLine() {
            int c = read();
            while (isSpaceChar(c))
                c = read();
            StringBuilder res = new StringBuilder();
            do {
                res.appendCodePoint(c);
                c = read();
            } while (!isEndOfLine(c));
            return res.toString();
        }

        public String nextString() {
            int c = read();
            while (isSpaceChar(c))
                c = read();
            StringBuilder res = new StringBuilder();
            do {
                res.appendCodePoint(c);
                c = read();
            } while (!isSpaceChar(c));
            return res.toString();
        }

        public long nextLong() {
            int c = read();
            while (isSpaceChar(c))
                c = read();
            int sgn = 1;
            if (c == '-') {
                sgn = -1;
                c = read();
            }
            long res = 0;
            do {
                if (c < '0' || c > '9')
                    throw new InputMismatchException();
                res *= 10;
                res += c - '0';
                c = read();
            } while (!isSpaceChar(c));
            return res * sgn;
        }

        public int nextInt() {
            int c = read();
            while (isSpaceChar(c))
                c = read();
            int sgn = 1;
            if (c == '-') {
                sgn = -1;
                c = read();
            }
            int res = 0;
            do {
                if (c < '0' || c > '9')
                    throw new InputMismatchException();
                res *= 10;
                res += c - '0';
                c = read();
            } while (!isSpaceChar(c));
            return res * sgn;
        }

        public int[] nextIntArray(int n) {
            return nextIntArray(n, 0);
        }

        public int[] nextIntArray(int n, int off) {
            int[] arr = new int[n + off];
            for (int i = 0; i < n; i++) {
                arr[i + off] = nextInt();
            }
            return arr;
        }

        public long[] nextLongArray(int n) {
            return nextLongArray(n, 0);
        }

        public long[] nextLongArray(int n, int off) {
            long[] arr = new long[n + off];
            for (int i = 0; i < n; i++) {
                arr[i + off] = nextLong();
            }
            return arr;
        }

        private boolean isSpaceChar(int c) {
            return c == ' ' || c == '\n' || c == '\r' || c == '\t' || c == -1;
        }

        private boolean isEndOfLine(int c) {
            return c == '\n' || c == '\r' || c == -1;
        }

        public void print(Object... objects) {
            for (int i = 0; i < objects.length; i++) {
                if (i != 0) {
                    writer.print(' ');
                }
                writer.print(objects[i]);
            }
        }

        public void println(Object... objects) {
            print(objects);
            writer.println();
        }

        public void printArray(int[] arr) {
            for (int i = 0; i < arr.length; i++) {
                if (i != 0) {
                    writer.print(' ');
                }
                writer.print(arr[i]);
            }
        }

        public void printArray(long[] arr) {
            for (int i = 0; i < arr.length; i++) {
                if (i != 0) {
                    writer.print(' ');
                }
                writer.print(arr[i]);
            }
        }

        public void printlnArray(int[] arr) {
            printArray(arr);
            writer.println();
        }

        public void printlnArray(long[] arr) {
            printArray(arr);
            writer.println();
        }

        public void printf(String format, Object... args) {
            print(String.format(format, args));
        }

        public void flush() {
            writer.flush();
        }
    }

    public static void main(String[] args) {
        FastIO io = new FastIO(System.in, System.out);
        solve(io);
        io.flush();
    }
}

import java.io.*;
import java.math.*;
import java.util.*;

public class ThreePieces {
    private static final int INF = 1234567890;

    public static void solve(FastIO io) {
        int N = io.nextInt();

        int r0 = -1, c0 = -1;
        int[][] A = new int[N][];
        for (int i = 0; i < N; ++i) {
            A[i] = io.nextIntArray(N);

            for (int j = 0; j < N; ++j) {
                if (A[i][j] == 1) {
                    r0 = i;
                    c0 = j;
                }
            }
        }

        LocList mb = new LocList();
        for (int i = 1; i <= N; ++i) {
            mb.add(new Loc(i, i));
            mb.add(new Loc(i, -i));
            mb.add(new Loc(-i, i));
            mb.add(new Loc(-i, -i));
        }

        LocList mk = new LocList();
        mk.add(new Loc(-2, 1));
        mk.add(new Loc(-1, 2));
        mk.add(new Loc(1, 2));
        mk.add(new Loc(2, 1));
        mk.add(new Loc(2, -1));
        mk.add(new Loc(1, -2));
        mk.add(new Loc(-1, -2));
        mk.add(new Loc(-2, -1));

        LocList mr = new LocList();
        for (int i = 1; i <= N; ++i) {
            mr.add(new Loc(0, i));
            mr.add(new Loc(0, -i));
            mr.add(new Loc(i, 0));
            mr.add(new Loc(-i, 0));
        }

        LocList[] moves = {mb, mk, mr};

        int N2 = N * N;
        int[][][][] dp = megaInit(N);
        dp[r0][c0][1][0] = dp[r0][c0][1][1] = dp[r0][c0][1][2] = 0;

        for (int t = 0;; ++t) {
            int[][][][] next = megaInit(N);
            int bestFinish = INF;
            for (int r = 0; r < N; ++r) {
                for (int c = 0; c < N; ++c) {
                    for (int i = 1; i < N2; ++i) {
                        for (int p = 0; p < 3; ++p) {
                            for (int np = 0; np < 3; ++np) {
                                next[r][c][i][np] = Math.min(next[r][c][i][np], 1 + dp[r][c][i][p]);
                            }
                            if (dp[r][c][i][p] < INF) {
                                for (Loc mv : moves[p]) {
                                    int nr = r + mv.R;
                                    int nc = c + mv.C;
                                    if (nr >= 0 && nr < N && nc >= 0 && nc < N) {
                                        int ni = i;
                                        if (A[nr][nc] == i + 1) {
                                            ++ni;
                                        }
                                        next[nr][nc][ni][p] = Math.min(next[nr][nc][ni][p], dp[r][c][i][p]);
                                    }
                                }
                            }
                        }
                    }
                    for (int p = 0; p < 3; ++p) {
                        bestFinish = Math.min(bestFinish, dp[r][c][N2][p]);
                        // if (dp[r][c][N2][p] < INF) {
                        //     System.out.format("r = %d, c = %d, p = %d, got: %d\n", r, c, p, dp[r][c][N2][p]);
                        // }
                    }
                }
            }
            if (bestFinish < INF) {
                io.printf("%d %d\n", t, bestFinish);
                return;
            }
            dp = next;
        }
    }

    private static int[][][][] megaInit(int N) {
        final int N2 = N * N;
        int[][][][] arr = new int[N][N][N2 + 1][3];
        for (int r = 0; r < N; ++r) {
            for (int c = 0; c < N; ++c) {
                for (int i = 0; i <= N2; ++i) {
                    Arrays.fill(arr[r][c][i], INF);
                }
            }
        }
        return arr;
    }

    private static class LocList extends ArrayList<Loc> {
    }

    private static class Loc {
        public int R, C;

        public Loc(int r, int c) {
            this.R = r;
            this.C = c;
        }
    }

    public static class FastIO {
        private InputStream reader;
        private PrintWriter writer;

        private byte[] buf = new byte[1024];
        private int curChar;
        private int numChars;

        public FastIO(InputStream r, OutputStream w) {
            reader = r;
            writer = new PrintWriter(new BufferedWriter(new OutputStreamWriter(w)));
        }

        public int read() {
            if (numChars == -1)
                throw new InputMismatchException();
            if (curChar >= numChars) {
                curChar = 0;
                try {
                    numChars = reader.read(buf);
                } catch (IOException e) {
                    throw new InputMismatchException();
                }
                if (numChars <= 0)
                    return -1;
            }
            return buf[curChar++];
        }

        public String nextLine() {
            int c = read();
            while (isSpaceChar(c))
                c = read();
            StringBuilder res = new StringBuilder();
            do {
                res.appendCodePoint(c);
                c = read();
            } while (!isEndOfLine(c));
            return res.toString();
        }

        public String nextString() {
            int c = read();
            while (isSpaceChar(c))
                c = read();
            StringBuilder res = new StringBuilder();
            do {
                res.appendCodePoint(c);
                c = read();
            } while (!isSpaceChar(c));
            return res.toString();
        }

        public long nextLong() {
            int c = read();
            while (isSpaceChar(c))
                c = read();
            int sgn = 1;
            if (c == '-') {
                sgn = -1;
                c = read();
            }
            long res = 0;
            do {
                if (c < '0' || c > '9')
                    throw new InputMismatchException();
                res *= 10;
                res += c - '0';
                c = read();
            } while (!isSpaceChar(c));
            return res * sgn;
        }

        public int nextInt() {
            int c = read();
            while (isSpaceChar(c))
                c = read();
            int sgn = 1;
            if (c == '-') {
                sgn = -1;
                c = read();
            }
            int res = 0;
            do {
                if (c < '0' || c > '9')
                    throw new InputMismatchException();
                res *= 10;
                res += c - '0';
                c = read();
            } while (!isSpaceChar(c));
            return res * sgn;
        }

        public int[] nextIntArray(int n) {
            return nextIntArray(n, 0);
        }

        public int[] nextIntArray(int n, int off) {
            int[] arr = new int[n + off];
            for (int i = 0; i < n; i++) {
                arr[i + off] = nextInt();
            }
            return arr;
        }

        public long[] nextLongArray(int n) {
            return nextLongArray(n, 0);
        }

        public long[] nextLongArray(int n, int off) {
            long[] arr = new long[n + off];
            for (int i = 0; i < n; i++) {
                arr[i + off] = nextLong();
            }
            return arr;
        }

        private boolean isSpaceChar(int c) {
            return c == ' ' || c == '\n' || c == '\r' || c == '\t' || c == -1;
        }

        private boolean isEndOfLine(int c) {
            return c == '\n' || c == '\r' || c == -1;
        }

        public void print(Object... objects) {
            for (int i = 0; i < objects.length; i++) {
                if (i != 0) {
                    writer.print(' ');
                }
                writer.print(objects[i]);
            }
        }

        public void println(Object... objects) {
            print(objects);
            writer.println();
        }

        public void printArray(int[] arr) {
            for (int i = 0; i < arr.length; i++) {
                if (i != 0) {
                    writer.print(' ');
                }
                writer.print(arr[i]);
            }
        }

        public void printArray(long[] arr) {
            for (int i = 0; i < arr.length; i++) {
                if (i != 0) {
                    writer.print(' ');
                }
                writer.print(arr[i]);
            }
        }

        public void printlnArray(int[] arr) {
            printArray(arr);
            writer.println();
        }

        public void printlnArray(long[] arr) {
            printArray(arr);
            writer.println();
        }

        public void printf(String format, Object... args) {
            print(String.format(format, args));
        }

        public void flush() {
            writer.flush();
        }
    }

    public static void main(String[] args) {
        FastIO io = new FastIO(System.in, System.out);
        solve(io);
        io.flush();
    }
}

/*
If you want to aim high, aim high
Don't let that studying and grades consume you
Just live life young
******************************
What do you think? What do you think?
1st on Billboard, what do you think of it
Next is a Grammy, what do you think of it
However you think, I’m sorry, but shit, I have no fcking interest
*******************************
I'm standing on top of my Monopoly board
That means I'm on top of my game and it don't stop
til my hip don't hop anymore
https://www.a2oj.com/Ladder16.html
*******************************
Shining through the city with a little funk and soul
*******************************
300iq as writer = Sad!
*/
import java.util.*;
import java.io.*;
import java.math.*;

   public class x1065D
   {
      public static void main(String hi[]) throws Exception
      {
         BufferedReader infile = new BufferedReader(new InputStreamReader(System.in));
         StringTokenizer st = new StringTokenizer(infile.readLine());
         int N = Integer.parseInt(st.nextToken());
         int[][] grid = new int[N][N];
         for(int i=0; i < N; i++)
         {
            st = new StringTokenizer(infile.readLine());
            for(int a=0; a < N; a++)
               grid[i][a] = Integer.parseInt(st.nextToken());
         }
         int[] loc = new int[N*N+1];
         for(int r=0; r < N; r++)
            for(int c=0; c < N; c++)
               loc[grid[r][c]] = r*N+c;
         //knight bishop rook
         int[][][] dist = new int[N*N+1][3][N*N+1];
         int[][][] replace = new int[N*N+1][3][N*N+1];
         for(int i=0; i <= N*N; i++)
            for(int p=0; p < 3; p++)
            {
               Arrays.fill(dist[i][p], 420420420);
               Arrays.fill(replace[i][p], 420420420);
            }
         int start = loc[1];
         for(int p=0; p < 3; p++)
            dist[start][p][1] = replace[start][p][1] = 0;
         ArrayDeque<Integer> q = new ArrayDeque<Integer>();
         for(int p=0; p < 3; p++)
         {
            q.add(start);
            q.add(p);
            q.add(1);
         }
         while(q.size() > 0)
         {
            int tempo = q.poll();
            int piece = q.poll();
            int target = q.poll();
            int x = tempo/N;
            int y = tempo%N;
            //swap
            for(int p=0; p < 3; p++)
            {
               if(piece == p)
                  continue;
               if(replace[tempo][p][target] > replace[tempo][piece][target]+1 && dist[tempo][p][target] == dist[tempo][piece][target]+1)
               {
                  replace[tempo][p][target] = replace[tempo][piece][target]+1;
                  q.add(tempo);
                  q.add(p);
                  q.add(target);
               }
               else if(dist[tempo][p][target] > dist[tempo][piece][target]+1)
               {
                  replace[tempo][p][target] = replace[tempo][piece][target]+1;
                  dist[tempo][p][target] = dist[tempo][piece][target]+1;
                  q.add(tempo);
                  q.add(p);
                  q.add(target);
               }
            }
            //move
            if(piece == 0)
            {
               for(int dx=-2; dx <= 2; dx++)
                  for(int dy=-2; dy <= 2; dy++)
                  {
                     if(dx == 0 || dy == 0 || Math.abs(dx) == Math.abs(dy))
                        continue;
                     int r = x+dx;
                     int c = y+dy;
                     if(Math.min(r,c) < 0 || Math.max(r,c) >= N)
                        continue;
                     int id = target+(grid[r][c]-1 == target ? 1 : 0);
                     if(dist[r*N+c][piece][id] > dist[tempo][piece][target]+1)
                     {
                        replace[r*N+c][piece][id] = replace[tempo][piece][target];
                        dist[r*N+c][piece][id] = dist[tempo][piece][target]+1;
                        q.add(r*N+c);
                        q.add(piece);
                        q.add(id);
                     }
                     else if(dist[r*N+c][piece][id] == dist[tempo][piece][target]+1 && replace[r*N+c][piece][id] > replace[tempo][piece][target])
                     {
                        replace[r*N+c][piece][id] = replace[tempo][piece][target];
                        q.add(r*N+c);
                        q.add(piece);
                        q.add(id);
                     }
                  }
            }
            else if(piece == 1)
            {
               for(int a=1; a < N; a++)
                  for(int dx=-1; dx <= 1; dx+=2)
                     for(int dy=-1; dy <= 1; dy+=2)
                     {
                        int r = x+dx*a;
                        int c = y+dy*a;
                        if(Math.min(r,c) < 0 || Math.max(r,c) >= N)
                           continue;
                        int id = target+(grid[r][c]-1 == target ? 1 : 0);
                        if(dist[r*N+c][piece][id] > dist[tempo][piece][target]+1)
                        {
                           replace[r*N+c][piece][id] = replace[tempo][piece][target];
                           dist[r*N+c][piece][id] = dist[tempo][piece][target]+1;
                           q.add(r*N+c);
                           q.add(piece);
                           q.add(id);
                        }
                        else if(dist[r*N+c][piece][id] == dist[tempo][piece][target]+1 && replace[r*N+c][piece][id] > replace[tempo][piece][target])
                        {
                           replace[r*N+c][piece][id] = replace[tempo][piece][target];
                           q.add(r*N+c);
                           q.add(piece);
                           q.add(id);
                        }
                     }
            }
            else
            {
               for(int a=1; a < N; a++)
                  for(int dx=-1; dx <= 1; dx+=2)
                  {
                     int r = x+dx*a;
                     int c = y;
                     if(Math.min(r,c) < 0 || Math.max(r,c) >= N)
                        continue;
                     int id = target+(grid[r][c]-1 == target ? 1 : 0);
                     if(dist[r*N+c][piece][id] > dist[tempo][piece][target]+1)
                     {
                        replace[r*N+c][piece][id] = replace[tempo][piece][target];
                        dist[r*N+c][piece][id] = dist[tempo][piece][target]+1;
                        q.add(r*N+c);
                        q.add(piece);
                        q.add(id);
                     }
                     else if(dist[r*N+c][piece][id] == dist[tempo][piece][target]+1 && replace[r*N+c][piece][id] > replace[tempo][piece][target])
                     {
                        replace[r*N+c][piece][id] = replace[tempo][piece][target];
                        q.add(r*N+c);
                        q.add(piece);
                        q.add(id);
                     }
                  }
               for(int a=1; a < N; a++)
                  for(int dy=-1; dy <= 1; dy+=2)
                  {
                     int r = x;
                     int c = y+dy*a;
                     if(Math.min(r,c) < 0 || Math.max(r,c) >= N)
                        continue;
                     int id = target+(grid[r][c]-1 == target ? 1 : 0);
                     if(dist[r*N+c][piece][id] > dist[tempo][piece][target]+1)
                     {
                        replace[r*N+c][piece][id] = replace[tempo][piece][target];
                        dist[r*N+c][piece][id] = dist[tempo][piece][target]+1;
                        q.add(r*N+c);
                        q.add(piece);
                        q.add(id);
                     }
                     else if(dist[r*N+c][piece][id] == dist[tempo][piece][target]+1 && replace[r*N+c][piece][id] > replace[tempo][piece][target])
                     {
                        replace[r*N+c][piece][id] = replace[tempo][piece][target];
                        q.add(r*N+c);
                        q.add(piece);
                        q.add(id);
                     }
                  }
            }
         }
         int minDist = 420420420;
         int minRep = 420420420;
         int lmao = N*N;
         for(int p=0; p < 3; p++)
         {
            if(minDist > dist[loc[lmao]][p][lmao])
            {
               minDist = dist[loc[lmao]][p][lmao];
               minRep = replace[loc[lmao]][p][lmao];
            }
            else if(minDist == dist[loc[lmao]][p][lmao] && minRep > replace[loc[lmao]][p][lmao])
               minRep = replace[loc[lmao]][p][lmao];
         }
         System.out.println(minDist+" "+minRep);
      }
   }

import java.io.OutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.Arrays;
import java.io.BufferedWriter;
import java.util.Collection;
import java.util.InputMismatchException;
import java.io.IOException;
import java.util.TreeSet;
import java.io.Writer;
import java.io.OutputStreamWriter;
import java.util.Queue;
import java.util.Comparator;
import java.util.ArrayDeque;
import java.io.InputStream;

/**
 * Built using CHelper plug-in
 * Actual solution is at the top
 *
 * @author t.me/musin_acm
 */
public class Main {
    public static void main(String[] args) {
        InputStream inputStream = System.in;
        OutputStream outputStream = System.out;
        InputReader in = new InputReader(inputStream);
        OutputWriter out = new OutputWriter(outputStream);
        D solver = new D();
        solver.solve(1, in, out);
        out.close();
    }

    static class D {
        int[][][][][] distanceByType;
        int n;
        int[][] pos;
        int A = 0;
        int B = 1;
        int C = 2;
        int[][][] turns;

        public void solve(int testNumber, InputReader in, OutputWriter out) {
            n = in.readInt();
            pos = new int[n * n][2];
            for (int i = 0; i < n; i++) {
                for (int j = 0; j < n; j++) {
                    int x = in.readInt() - 1;
                    pos[x][0] = i;
                    pos[x][1] = j;
                }
            }
            initAll();
            IntIntPair ans = solve();
            out.print(ans.first, ans.second);
        }

        IntIntPair solve() {
            int[][][] dp = new int[n * n][3][20 * 100];
            ArrayUtils.fill(dp, Integer.MAX_VALUE);
            dp[0][0][0] = dp[0][1][0] = dp[0][2][0] = 0;
            for (int i = 0; i + 1 < n * n; i++) {
                int x0 = pos[i][0];
                int y0 = pos[i][1];
                int x1 = pos[i + 1][0];
                int y1 = pos[i + 1][1];
                for (int t0 = 0; t0 < 3; t0++) {
                    IntIntPair[] res = go(x0, y0, t0, x1, y1);
                    for (int t1 = 0; t1 < 3; t1++) {
                        if (res[t1] == null) {
                            continue;
                        }
                        for (int prevRes = 0; prevRes < 20 * 100; prevRes++) {
                            if (dp[i][t0][prevRes] == Integer.MAX_VALUE) {
                                continue;
                            }
                            dp[i + 1][t1][prevRes + res[t1].first] = Math.min(
                                    dp[i + 1][t1][prevRes + res[t1].first],
                                    dp[i][t0][prevRes] + res[t1].second
                            );
                        }
                    }
                }
            }
            IntIntPair ans = IntIntPair.makePair(Integer.MAX_VALUE, Integer.MAX_VALUE);
            for (int res = 0; res < 20 * 100; res++) {
                for (int t = 0; t < 3; t++) {
                    if (dp[n * n - 1][t][res] == Integer.MAX_VALUE) {
                        continue;
                    }
                    IntIntPair cur = IntIntPair.makePair(res, dp[n * n - 1][t][res]);
                    if (cur.compareTo(ans) < 0) {
                        ans = cur;
                    }
                }
            }
            return ans;
        }

        IntIntPair[] go(int x0, int y0, int t0, int x1, int y1) {

            IntIntPair[][][] dp = new IntIntPair[n][n][3];

            TreeSet<Pair<IntIntPair, Integer>> q = new TreeSet<>(Comparator
                    .<Pair<IntIntPair, Integer>, IntIntPair>comparing(
                            p1 -> dp[p1.first.first][p1.first.second][p1.second])
                    .thenComparing(p1 -> p1));
            dp[x0][y0][t0] = IntIntPair.makePair(0, 0);
            q.add(Pair.makePair(IntIntPair.makePair(x0, y0), t0));
            while (!q.isEmpty()) {
                Pair<IntIntPair, Integer> cur = q.pollFirst();
                int curX = cur.first.first;
                int curY = cur.first.second;
                int curT = cur.second;
                IntIntPair curRes = dp[curX][curY][curT];
                for (int t = 0; t < 3; t++) {
                    int[][] turns = this.turns[t];
                    int addT = t == curT ? 0 : 1;
                    for (int[] turn : turns) {
                        int x = curX + turn[0];
                        int y = curY + turn[1];
                        if (inside(x, y)) {
                            IntIntPair p = IntIntPair.makePair(curRes.first + 1 + addT, curRes.second + addT);
                            if (dp[x][y][t] == null || better(p, dp[x][y][t])) {
                                Pair<IntIntPair, Integer> curP = Pair.makePair(IntIntPair.makePair(x, y), t);
                                if (dp[x][y][t] != null) {
                                    q.remove(curP);
                                }
                                dp[x][y][t] = p;
                                q.add(curP);
                            }
                        }
                    }
                }
            }
            return dp[x1][y1];
        }

        boolean better(IntIntPair p1, IntIntPair p2) {
            return p1.compareTo(p2) < 0;
        }

        void initAll() {
            initTurns();
            initDistances();
        }

        void initDistances() {
            distanceByType = new int[3][][][][];
            for (int i = 0; i < 3; i++) {
                distanceByType[i] = goBySingleType(i);
            }
        }

        int[][][][] goBySingleType(int type) {
            int[][][][] dp = new int[n][n][][];
            for (int i = 0; i < n; i++) {
                for (int j = 0; j < n; j++) {
                    dp[i][j] = goBySingleType(type, i, j);
                }
            }
            return dp;
        }

        int[][] goBySingleType(int type, int x0, int y0) {
            int[][] dp = new int[n][n];
            ArrayUtils.fill(dp, -1);
            dp[x0][y0] = 0;
            Queue<IntIntPair> q = new ArrayDeque<>();
            q.add(IntIntPair.makePair(x0, y0));
            while (!q.isEmpty()) {
                IntIntPair cur = q.poll();
                for (int[] turn : turns[type]) {
                    int x = cur.first + turn[0];
                    int y = cur.second + turn[1];
                    if (inside(x, y) && dp[x][y] == -1) {
                        dp[x][y] = dp[cur.first][cur.second] + 1;
                        q.add(IntIntPair.makePair(x, y));
                    }
                }
            }
            return dp;
        }

        boolean inside(int x, int y) {
            return 0 <= x && x < n && 0 <= y && y < n;
        }

        void initTurns() {
            turns = new int[3][][];
            turns[A] = turnsA();
            turns[B] = turnsB();
            turns[C] = turnsC();
        }

        int[][] turnsA() {
            int[] dx = {-1, -2, -2, -1, 1, 2, 2, 1};
            int[] dy = {-2, -1, 1, 2, 2, 1, -1, -2};
            int[][] d = new int[8][2];
            for (int i = 0; i < 8; i++) {
                d[i][0] = dx[i];
                d[i][1] = dy[i];
            }
            return d;
        }

        int[][] turnsB() {
            int[][] t = new int[2 * (2 * n - 1)][2];
            int at = 0;
            for (int i = 0, x = -n + 1, y = -n + 1; i < 2 * n - 1; i++, at++) {
                t[at][0] = x;
                t[at][1] = y;
                x += 1;
                y += 1;
            }
            for (int i = 0, x = -n + 1, y = n - 1; i < 2 * n - 1; i++, at++) {
                t[at][0] = x;
                t[at][1] = y;
                x += 1;
                y -= 1;
            }
            return t;
        }

        int[][] turnsC() {
            int[][] t = new int[2 * (2 * n - 1)][2];
            int at = 0;
            for (int i = 0, x = -n + 1, y = 0; i < 2 * n - 1; i++, at++) {
                t[at][0] = x;
                t[at][1] = y;
                x += 1;
            }
            for (int i = 0, x = 0, y = -n + 1; i < 2 * n - 1; i++, at++) {
                t[at][0] = x;
                t[at][1] = y;
                y += 1;
            }
            return t;
        }

    }

    static class OutputWriter {
        private final PrintWriter writer;

        public OutputWriter(OutputStream outputStream) {
            writer = new PrintWriter(new BufferedWriter(new OutputStreamWriter(outputStream)));
        }

        public OutputWriter(Writer writer) {
            this.writer = new PrintWriter(writer);
        }

        public void print(Object... objects) {
            for (int i = 0; i < objects.length; i++) {
                if (i != 0) {
                    writer.print(' ');
                }
                writer.print(objects[i]);
            }
        }

        public void close() {
            writer.close();
        }

    }

    static class InputReader {
        private InputStream stream;
        private byte[] buf = new byte[1024];
        private int curChar;
        private int numChars;
        private InputReader.SpaceCharFilter filter;

        public InputReader(InputStream stream) {
            this.stream = stream;
        }

        public int read() {
            if (numChars == -1) {
                throw new InputMismatchException();
            }
            if (curChar >= numChars) {
                curChar = 0;
                try {
                    numChars = stream.read(buf);
                } catch (IOException e) {
                    throw new InputMismatchException();
                }
                if (numChars <= 0) {
                    return -1;
                }
            }
            return buf[curChar++];
        }

        public int readInt() {
            int c = read();
            while (isSpaceChar(c)) {
                c = read();
            }
            int sgn = 1;
            if (c == '-') {
                sgn = -1;
                c = read();
            }
            int res = 0;
            do {
                if (c < '0' || c > '9') {
                    throw new InputMismatchException();
                }
                res *= 10;
                res += c - '0';
                c = read();
            } while (!isSpaceChar(c));
            return res * sgn;
        }

        public boolean isSpaceChar(int c) {
            if (filter != null) {
                return filter.isSpaceChar(c);
            }
            return isWhitespace(c);
        }

        public static boolean isWhitespace(int c) {
            return c == ' ' || c == '\n' || c == '\r' || c == '\t' || c == -1;
        }

        public interface SpaceCharFilter {
            public boolean isSpaceChar(int ch);

        }

    }

    static class ArrayUtils {
        public static void fill(int[][] array, int value) {
            for (int[] row : array) {
                Arrays.fill(row, value);
            }
        }

        public static void fill(int[][][] array, int value) {
            for (int[][] subArray : array) {
                fill(subArray, value);
            }
        }

    }

    static class IntIntPair implements Comparable<IntIntPair> {
        public final int first;
        public final int second;

        public static IntIntPair makePair(int first, int second) {
            return new IntIntPair(first, second);
        }

        public IntIntPair(int first, int second) {
            this.first = first;
            this.second = second;
        }

        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (o == null || getClass() != o.getClass()) {
                return false;
            }

            IntIntPair pair = (IntIntPair) o;

            return first == pair.first && second == pair.second;
        }

        public int hashCode() {
            int result = first;
            result = 31 * result + second;
            return result;
        }

        public String toString() {
            return "(" + first + "," + second + ")";
        }

        public int compareTo(IntIntPair o) {
            int value = Integer.compare(first, o.first);
            if (value != 0) {
                return value;
            }
            return Integer.compare(second, o.second);
        }

    }

    static class Pair<U, V> implements Comparable<Pair<U, V>> {
        public final U first;
        public final V second;

        public static <U, V> Pair<U, V> makePair(U first, V second) {
            return new Pair<U, V>(first, second);
        }

        private Pair(U first, V second) {
            this.first = first;
            this.second = second;
        }

        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (o == null || getClass() != o.getClass()) {
                return false;
            }

            Pair pair = (Pair) o;

            return !(first != null ? !first.equals(pair.first) : pair.first != null) &&
                    !(second != null ? !second.equals(pair.second) : pair.second != null);
        }

        public int hashCode() {
            int result = first != null ? first.hashCode() : 0;
            result = 31 * result + (second != null ? second.hashCode() : 0);
            return result;
        }

        public String toString() {
            return "(" + first + "," + second + ")";
        }

        public int compareTo(Pair<U, V> o) {
            int value = ((Comparable<U>) first).compareTo(o.first);
            if (value != 0) {
                return value;
            }
            return ((Comparable<V>) second).compareTo(o.second);
        }

    }
}

import java.util.*;
import java.io.*;

public class Solution{
	static class FastReader
    {
        BufferedReader br;
        StringTokenizer st;

        public FastReader()
        {
            br = new BufferedReader(new
                     InputStreamReader(System.in));
        }

        String next()
        {
            while (st == null || !st.hasMoreElements())
            {
                try
                {
                    st = new StringTokenizer(br.readLine());
                }
                catch (IOException  e)
                {
                    e.printStackTrace();
                }
            }
            return st.nextToken();
        }

        int nextInt()
        {
            return Integer.parseInt(next());
        }

        long nextLong()
        {
            return Long.parseLong(next());
        }

        double nextDouble()
        {
            return Double.parseDouble(next());
        }

        String nextLine()
        {
            String str = "";
            try
            {
                str = br.readLine();
            }
            catch (IOException e)
            {
                e.printStackTrace();
            }
            return str;
        }
    }

	static class Node{
		int x,y,val,st,rep, type;
		public Node(int a1, int a2, int a3, int a4, int a5, int a6){
			val=a1; x=a2; y=a3; rep=a4; st=a5; type=a6;
		}

		public int getR(){
			return this.rep;
		}

		public int getSt(){
			return this.st;
		}
	}

	static public void main(String[] args){
		FastReader sc = new FastReader();

		int n = sc.nextInt();
		PriorityQueue<Node> q = new PriorityQueue<Node>(Comparator.comparingInt(Node::getSt).thenComparingInt(Node::getR));

		boolean vis[][][][] = new boolean[n*n+1][n+1][n+1][3];
		int[][][] move = new int[3][][];

		move[0] = new int[][]{{1,1}, {1,-1}, {-1,1}, {-1,-1}};
		move[1] = new int[][]{{0,1}, {1,0}, {-1,0}, {0,-1}};
		move[2] = new int[][]{{2,1}, {-1,-2}, {1,2}, {-1,2}, {1,-2}, {-2,1}, {-2,-1}, {2,-1} };

		int[][]pos = new int[n*n+1][2];

		for(int i=1;i<=n;i++)
			for(int j=1;j<=n;j++)
				pos[sc.nextInt()] = new int[]{i,j};

		q.add(new Node(1, pos[1][0], pos[1][1], 0, 0, 0));
		q.add(new Node(1, pos[1][0], pos[1][1], 0, 0, 1));
		q.add(new Node(1, pos[1][0], pos[1][1], 0, 0, 2));

		while(q.size()>0){
			Node cur = q.poll();
			if(cur.x<=0 || cur.x>n || cur.y<=0 || cur.y>n)	continue;

			if(vis[cur.val][cur.x][cur.y][cur.type]) continue;

			vis[cur.val][cur.x][cur.y][cur.type] = true;

			if(cur.x == pos[cur.val+1][0] && cur.y == pos[cur.val+1][1]){
				cur.val++;
				if(cur.val==n*n){
					System.out.println(cur.st+" "+cur.rep);
					return;
				}
				vis[cur.val][cur.x][cur.y][cur.type] = true;
			}

			for(int i=0;i<3;i++)
				q.add(new Node(cur.val, cur.x, cur.y, cur.rep+1, cur.st+1, i));

			for(int[] m : move[cur.type]){
				if(cur.type==2)
					q.add(new Node(cur.val, cur.x+m[0], cur.y+m[1], cur.rep, cur.st+1, cur.type));
				else
					for(int i=1;i<=n;i++)
						q.add(new Node(cur.val, cur.x+m[0]*i, cur.y+m[1]*i, cur.rep, cur.st+1, cur.type));
			}
		}

	}
}

import java.util.*;
import java.io.*;

public class Main {
	public static void main(String args[]) {new Main().run();}

	FastReader in = new FastReader();
	PrintWriter out = new PrintWriter(System.out);
	void run(){
		work();
		out.flush();
	}
	long mod=1000000007;
	long inf=Long.MAX_VALUE;
	long gcd(long a,long b) {
		return a==0?b:gcd(b%a,a);
	}
	int n;
	int[][] dir=new int[][] {{1,2},{2,1},{-1,2},{-2,1},{1,-2},{2,-1},{-1,-2},{-2,-1}};
	void work() {
		n=ni();
		int[][] rec=new int[n*n][];
		for(int i=0;i<n;i++) {
			for(int j=0;j<n;j++) {
				rec[ni()-1]=new int[] {i,j};
			}
		}
		int[][] dp=new int[3][2];//车相马,step replacement
		for(int i=0;i<n*n-1;i++) {
			int[][] ndp=new int[3][2];
			for(int[] d:ndp) {
				Arrays.fill(d, 99999999);
			}
			for(int j=0;j<3;j++) {
				int[][] A=bfs(rec[i],rec[i+1],j);
				for(int k=0;k<3;k++) {
					int v1=dp[j][0]+A[k][0];
					int v2=dp[j][1]+A[k][1];
					if(v1<ndp[k][0]||(v1==ndp[k][0]&&v2<ndp[k][1])) {
						ndp[k][0]=v1;
						ndp[k][1]=v2;
					}
				}
			}
			dp=ndp;
		}
		int s=99999999,r=99999999;
		for(int[] d:dp) {
			if(d[0]<s) {
				s=d[0];
				r=d[1];
			}else if(d[0]==s&&d[1]<r) {
				r=d[1];
			}
		}
		out.println(s+" "+r);
	}

	private int[][] bfs(int[] start, int[] end, int state) {
		int[][][][] rec=new int[n][n][3][2];
		for(int i=0;i<n;i++) {
			for(int j=0;j<n;j++) {
				for(int k=0;k<3;k++) {
					rec[i][j][k][0]=99999999;
					rec[i][j][k][1]=99999999;
				}
			}
		}
		rec[start[0]][start[1]][state][0]=0;
		rec[start[0]][start[1]][state][1]=0;
		LinkedList<int[]> queue=new LinkedList<>();
		queue.add(new int[] {start[0],start[1],0,0,state});
		while(queue.size()>0) {
			int[] q=queue.poll();
			int x=q[0],y=q[1],step=q[2],rep=q[3],s=q[4];
			for(int i=0;i<3;i++) {
				int nstep=step+1;
				int nrep=rep+1;
				if(rec[x][y][i][0]>nstep||(rec[x][y][i][0]==nstep&&rec[x][y][i][1]>nrep)) {
					rec[x][y][i][0]=nstep;
					rec[x][y][i][1]=nrep;
					queue.add(new int[] {x,y,nstep,nrep,i});
				}
			}
			if(s==0) {
				for(int i=0;i<n;i++) {
					int nstep=step+1;
					if(rec[i][y][s][0]>nstep||(rec[i][y][s][0]==nstep&&rec[i][y][s][1]>rep)) {
						rec[i][y][s][0]=nstep;
						rec[i][y][s][1]=rep;
						queue.add(new int[] {i,y,nstep,rep,s});
					}
					if(rec[x][i][s][0]>nstep||(rec[x][i][s][0]==nstep&&rec[x][i][s][1]>rep)) {
						rec[x][i][s][0]=nstep;
						rec[x][i][s][1]=rep;
						queue.add(new int[] {x,i,nstep,rep,s});
					}
				}
			}else if(s==1) {
				for(int i=-n;i<n;i++) {
					int nstep=step+1;
					int nx=x+i,ny=y+i;
					if(nx>=0&&nx<n&&ny>=0&&ny<n) {
						if(rec[nx][ny][s][0]>nstep||(rec[nx][ny][s][0]==nstep&&rec[nx][ny][s][1]>rep)) {
							queue.add(new int[] {nx,ny,nstep,rep,s});
							rec[nx][ny][s][0]=nstep;
							rec[nx][ny][s][1]=rep;
						}
					}
					nx=x-i;ny=y+i;
					if(nx<0||nx>=n||ny<0||ny>=n)continue;
					if(rec[nx][ny][s][0]>nstep||(rec[nx][ny][s][0]==nstep&&rec[nx][ny][s][1]>rep)) {
						queue.add(new int[] {nx,ny,nstep,rep,s});
						rec[nx][ny][s][0]=nstep;
						rec[nx][ny][s][1]=rep;
					}
				}
			}else {
				for(int i=0;i<8;i++) {
					int nx=x+dir[i][0],ny=y+dir[i][1];
					int nstep=step+1;
					if(nx<0||nx>=n||ny<0||ny>=n)continue;
					if(rec[nx][ny][s][0]>nstep||(rec[nx][ny][s][0]==nstep&&rec[nx][ny][s][1]>rep)) {
						queue.add(new int[] {nx,ny,nstep,rep,s});
						rec[nx][ny][s][0]=nstep;
						rec[nx][ny][s][1]=rep;
					}
				}
			}
		}
		return rec[end[0]][end[1]];
	}

	//input
	@SuppressWarnings("unused")
	private ArrayList<Integer>[] ng(int n, int m) {
		ArrayList<Integer>[] graph=(ArrayList<Integer>[])new ArrayList[n];
		for(int i=0;i<n;i++) {
			graph[i]=new ArrayList<>();
		}
		for(int i=1;i<=m;i++) {
			int s=in.nextInt()-1,e=in.nextInt()-1;
			graph[s].add(e);
			graph[e].add(s);
		}
		return graph;
	}

	private ArrayList<long[]>[] ngw(int n, int m) {
		ArrayList<long[]>[] graph=(ArrayList<long[]>[])new ArrayList[n];
		for(int i=0;i<n;i++) {
			graph[i]=new ArrayList<>();
		}
		for(int i=1;i<=m;i++) {
			long s=in.nextLong()-1,e=in.nextLong()-1,w=in.nextLong();
			graph[(int)s].add(new long[] {e,w,i});
			graph[(int)e].add(new long[] {s,w});
		}
		return graph;
	}

	private int ni() {
		return in.nextInt();
	}

	private long nl() {
		return in.nextLong();
	}

	private String ns() {
		return in.next();
	}

	private long[] na(int n) {
		long[] A=new long[n];
		for(int i=0;i<n;i++) {
			A[i]=in.nextLong();
		}
		return A;
	}
	private int[] nia(int n) {
		int[] A=new int[n];
		for(int i=0;i<n;i++) {
			A[i]=in.nextInt();
		}
		return A;
	}
}

class FastReader
{
	BufferedReader br;
	StringTokenizer st;

	public FastReader()
	{
		br=new BufferedReader(new InputStreamReader(System.in));
	}

	public String next()
	{
		while(st==null || !st.hasMoreElements())//回车，空行情况
		{
			try {
				st = new StringTokenizer(br.readLine());
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return st.nextToken();
	}

	public int nextInt()
	{
		return Integer.parseInt(next());
	}

	public long nextLong()
	{
		return Long.parseLong(next());
	}
}

import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;

public class P1065D {

	static class Node{
		int i, j;
		int minNumber;
		int replacements;
		int currentPiece; //0-knight, 1--rook, 2-bishop
		int dist;

		public Node(int a, int b, int c, int d, int e, int f) {
			i=a;
			j=b;
			minNumber=c;
			replacements=d;
			currentPiece=e;
			dist=f;
		}
	}

	static int n;
	static int[][] g;
	static boolean[][][][][] mark;
	static int[][][][][] dist;

	static int[] kdx = {1, 2, 1, 2, -1, -2, -1, -2};
	static int[] kdy = {2, 1, -2, -1, 2, 1, -2, -1};

	public static void main(String[] args) {
		Scanner in = new Scanner(System.in);
		n = in.nextInt();
		g = new int[n][n];
		int sx=0, sy=0;
		int ex=0, ey=0;
		for(int i=0; i<n; i++) {
			for(int j=0; j<n; j++) {
				g[i][j] = in.nextInt()-1;
				if (g[i][j]==0) {
					sx=i;
					sy=j;
				} else if (g[i][j]==n*n-1) {
					ex=i;
					ey=j;
				}
			}
		}

		mark = new boolean[n][n][n*n+1][n*n+1][3];
		dist = new int[n][n][n*n+1][n*n+1][3];
		for(int i=0; i<n; i++) {
			for(int j=0; j<n; j++) {
				for(int p=0; p<n*n+1; p++) {
					for(int q=0; q<n*n+1; q++) {
						for(int z=0; z<3; z++) {
							dist[i][j][p][q][z] = (int)1e8;
						}
					}
				}
			}
		}

		Queue<Node> queue = new LinkedList<>();
		dist[sx][sy][0][0][0]=0;
		dist[sx][sy][0][0][1]=0;
		dist[sx][sy][0][0][2]=0;
		queue.add(new Node(sx, sy, 0, 0, 0, 0));
		queue.add(new Node(sx, sy, 0, 0, 1, 0));
		queue.add(new Node(sx, sy, 0, 0, 2, 0));

		while(!queue.isEmpty()) {
//			System.out.println("POP");
			Node node = queue.poll();
			int x = node.i;
			int y = node.j;
			//remember to add the state with replacement

			//PIECE REPLACEMENT
			for(int np=0; np<3; np++) {
				if (np==node.currentPiece) continue;
				int nx=node.i;
				int ny=node.j;
				if (node.replacements==n*n) continue;
				if (mark[nx][ny][node.minNumber][node.replacements+1][np]) continue;
				queue.add(new Node(nx, ny, node.minNumber, node.replacements+1, np, node.dist+1));
				mark[nx][ny][node.minNumber][node.replacements+1][np]=true;
				dist[nx][ny][node.minNumber][node.replacements+1][np]=node.dist+1;
			}

			if (node.currentPiece==0) {
				//KNIGHT
				for(int k=0; k<8; k++) {
					int nx = x+kdx[k];
					int ny = y+kdy[k];
					if (nx<0 || nx>=n || ny<0 || ny>=n) continue;
					int newMinNumber = node.minNumber;
					if (g[nx][ny]==node.minNumber+1) {
						newMinNumber = node.minNumber+1;
					}
					if (mark[nx][ny][newMinNumber][node.replacements][node.currentPiece]) continue;
					queue.add(new Node(nx, ny, newMinNumber, node.replacements, node.currentPiece, node.dist+1));
					mark[nx][ny][newMinNumber][node.replacements][node.currentPiece]=true;
					dist[nx][ny][newMinNumber][node.replacements][node.currentPiece] = node.dist+1;
				}
			} else if (node.currentPiece==1) {
				//ROOK
				for(int i=0; i<n; i++) {
					if (i==node.i) continue;
					//to i, node.y
					int nx = i;
					int ny = node.j;
					int newMinNumber = node.minNumber;
					if (g[nx][ny]==node.minNumber+1) {
						newMinNumber = node.minNumber+1;
					}
					if (mark[nx][ny][newMinNumber][node.replacements][node.currentPiece]) continue;
					queue.add(new Node(nx, ny, newMinNumber, node.replacements, node.currentPiece, node.dist+1));
					mark[nx][ny][newMinNumber][node.replacements][node.currentPiece]=true;
					dist[nx][ny][newMinNumber][node.replacements][node.currentPiece]=node.dist+1;
				}
				for(int j=0; j<n; j++) {
					if (j==node.j) continue;
					//to node.x, j
					int nx = node.i;
					int ny = j;
					int newMinNumber = node.minNumber;
					if (g[nx][ny]==node.minNumber+1) {
						newMinNumber = node.minNumber+1;
					}
					if (mark[nx][ny][newMinNumber][node.replacements][node.currentPiece]) continue;
					queue.add(new Node(nx, ny, newMinNumber, node.replacements, node.currentPiece, node.dist+1));
					mark[nx][ny][newMinNumber][node.replacements][node.currentPiece]=true;
					dist[nx][ny][newMinNumber][node.replacements][node.currentPiece]=node.dist+1;
				}
			} else {
				//BISHOP
				for(int i=node.i, j=node.j; i<n && j<n; i++, j++) {
					if (i==node.i && j==node.j) continue;
					int nx = i;
					int ny = j;
					int newMinNumber = node.minNumber;
					if (g[nx][ny]==node.minNumber+1) {
						newMinNumber = node.minNumber+1;
					}
					if (mark[nx][ny][newMinNumber][node.replacements][node.currentPiece]) continue;
					queue.add(new Node(nx, ny, newMinNumber, node.replacements, node.currentPiece, node.dist+1));
					mark[nx][ny][newMinNumber][node.replacements][node.currentPiece]=true;
					dist[nx][ny][newMinNumber][node.replacements][node.currentPiece] = node.dist+1;
				}
				for(int i=node.i, j=node.j; i<n && j>=0; i++, j--) {
					if (i==node.i && j==node.j) continue;
					int nx = i;
					int ny = j;
					int newMinNumber = node.minNumber;
					if (g[nx][ny]==node.minNumber+1) {
						newMinNumber = node.minNumber+1;
					}
					if (mark[nx][ny][newMinNumber][node.replacements][node.currentPiece]) continue;
					queue.add(new Node(nx, ny, newMinNumber, node.replacements, node.currentPiece, node.dist+1));
					mark[nx][ny][newMinNumber][node.replacements][node.currentPiece]=true;
					dist[nx][ny][newMinNumber][node.replacements][node.currentPiece] = node.dist+1;
				}
				for(int i=node.i, j=node.j; i>=0 && j<n; i--, j++) {
					if (i==node.i && j==node.j) continue;
					int nx = i;
					int ny = j;
					int newMinNumber = node.minNumber;
					if (g[nx][ny]==node.minNumber+1) {
						newMinNumber = node.minNumber+1;
					}
					if (mark[nx][ny][newMinNumber][node.replacements][node.currentPiece]) continue;
					queue.add(new Node(nx, ny, newMinNumber, node.replacements, node.currentPiece, node.dist+1));
					mark[nx][ny][newMinNumber][node.replacements][node.currentPiece]=true;
					dist[nx][ny][newMinNumber][node.replacements][node.currentPiece] = node.dist+1;
				}
				for(int i=node.i, j=node.j; i>=0 && j>=0; i--, j--) {
					if (i==node.i && j==node.j) continue;
					int nx = i;
					int ny = j;
					int newMinNumber = node.minNumber;
					if (g[nx][ny]==node.minNumber+1) {
						newMinNumber = node.minNumber+1;
					}
					if (mark[nx][ny][newMinNumber][node.replacements][node.currentPiece]) continue;
					queue.add(new Node(nx, ny, newMinNumber, node.replacements, node.currentPiece, node.dist+1));
					mark[nx][ny][newMinNumber][node.replacements][node.currentPiece]=true;
					dist[nx][ny][newMinNumber][node.replacements][node.currentPiece] = node.dist+1;
				}
			}
		}

		long ans=(long)1e15;
		int minrep = (int)1e8;
		for(int replacement=0; replacement<=n*n; replacement++) {
			for(int cp=0; cp<3; cp++) {
				if(ans>dist[ex][ey][n*n-1][replacement][cp]) {
					minrep=replacement;
					ans = dist[ex][ey][n*n-1][replacement][cp];
				}
			}

		}
		System.out.println(ans+" "+minrep);

	}

}

import java.io.OutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.StringTokenizer;
import java.io.IOException;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.InputStream;
import java.util.ArrayList;

/**
 * Built using CHelper plug-in
 * Actual solution is at the top
 */
public class Main {
    public static void main(String[] args) {
        InputStream inputStream = System.in;
        OutputStream outputStream = System.out;
        InputReader in = new InputReader(inputStream);
        PrintWriter out = new PrintWriter(outputStream);
        ThreePieces solver = new ThreePieces();
        solver.solve(1, in, out);
        out.close();
    }

    static class ThreePieces {
        int[][][][][] dp = new int[201][10][10][100][3];
        int INF = (int) (1e9);
        ArrayList<int[]>[][][] move = new ArrayList[10][10][3];
        int N;
        int[] moveR = new int[]{-2, -1, 1, 2};
        int[] moveC = new int[]{-2, -1, 1, 2};

        public void solve(int testNumber, InputReader in, PrintWriter out) {
            N = in.nextInt();
            int[][] loc = new int[N * N][2];
            int[][] arr = new int[N][N];
            for (int i = 0; i < N; i++) {
                for (int j = 0; j < N; j++) {
                    int a = in.nextInt() - 1;
                    loc[a] = new int[]{i, j};
                    arr[i][j] = a;
                    move[i][j][0] = posKnight(i, j);
                    move[i][j][1] = posBishop(i, j);
                    move[i][j][2] = posRook(i, j);
                }
            }
            for (int i = 0; i <= 200; i++) {
                for (int j = 0; j < 10; j++) {
                    for (int k = 0; k < 10; k++) {
                        for (int d = 0; d < 100; d++) {
                            for (int z = 0; z < 3; z++) {
                                dp[i][j][k][d][z] = INF;
                            }
                        }
                    }
                }
            }
            Arrays.fill(dp[0][loc[0][0]][loc[0][1]][0], 0);
            for (int i = 0; i < 200; i++) {
                for (int j = 0; j < N; j++) {
                    for (int k = 0; k < N; k++) {
                        for (int d = 0; d < N * N; d++) {
                            for (int m = 0; m < 3; m++) {
                                for (int next = 0; next < 3; next++) {
                                    if (next == m) {
                                        for (int[] c : move[j][k][next]) {
                                            if (arr[c[0]][c[1]] == d + 1) {
                                                dp[i + 1][c[0]][c[1]][d + 1][next] = Math.min(dp[i + 1][c[0]][c[1]][d + 1][next], dp[i][j][k][d][m]);
                                            } else {
                                                dp[i + 1][c[0]][c[1]][d][next] = Math.min(dp[i + 1][c[0]][c[1]][d][next], dp[i][j][k][d][m]);
                                            }
                                        }
                                    } else {
                                        dp[i + 1][j][k][d][next] = Math.min(dp[i + 1][j][k][d][next], dp[i][j][k][d][m] + 1);
                                    }
                                }
                            }
                        }
                    }
                }
            }
            for (int i = 2; i <= 200; i++) {
                if (has(dp[i][loc[N * N - 1][0]][loc[N * N - 1][1]][N * N - 1])) {
                    int res = INF;
                    for (int v : dp[i][loc[N * N - 1][0]][loc[N * N - 1][1]][N * N - 1]) {
                        res = Math.min(res, v);
                    }
                    out.println(i + " " + res);
                    return;
                }
            }
        }

        boolean has(int[] a) {
            for (int i : a) {
                if (i != INF) {
                    return true;
                }
            }
            return false;
        }

        boolean isValid(int r, int c) {
            return r >= 0 && r < N && c >= 0 && c < N;
        }

        ArrayList<int[]> posKnight(int r, int c) {
            ArrayList<int[]> pos = new ArrayList<>();
            for (int rM : moveR) {
                for (int cM : moveC) {
                    if (Math.abs(rM) != Math.abs(cM)) {
                        int newR = r + rM;
                        int newC = c + cM;
                        if (isValid(newR, newC)) {
                            pos.add(new int[]{newR, newC});
                        }
                    }
                }
            }
            return pos;
        }

        ArrayList<int[]> posRook(int r, int c) {
            ArrayList<int[]> pos = new ArrayList<>();
            for (int i = 0; i < N; i++) {
                pos.add(new int[]{r, i});
                pos.add(new int[]{i, c});
            }
            return pos;
        }

        ArrayList<int[]> posBishop(int r, int c) {
            ArrayList<int[]> pos = new ArrayList<>();
            for (int k = 1; k < N; k++) {
                int[] mR = new int[]{k, -k, k, -k};
                int[] mC = new int[]{k, k, -k, -k};
                for (int i = 0; i < 4; i++) {
                    int newR = r + mR[i];
                    int newC = c + mC[i];
                    if (isValid(newR, newC)) {
                        pos.add(new int[]{newR, newC});
                    }
                }
            }
            return pos;
        }

    }

    static class InputReader {
        public BufferedReader reader;
        public StringTokenizer tokenizer;

        public InputReader(InputStream stream) {
            reader = new BufferedReader(new InputStreamReader(stream), 32768);
            tokenizer = null;
        }

        public String next() {
            while (tokenizer == null || !tokenizer.hasMoreTokens()) {
                try {
                    tokenizer = new StringTokenizer(reader.readLine());
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
            return tokenizer.nextToken();
        }

        public int nextInt() {
            return Integer.parseInt(next());
        }

    }
}

import java.util.PriorityQueue;
import java.util.Scanner;

public class Main {

	private static class Element implements Comparable<Element> {
		public int x;
		public int y;
		public int nextValue;
		public int type;
		public int changeTime;
		public int dist;
		public Element(int x , int y , int nextValue , int type , int changeTime , int dist) {
			this.x = x;
			this.y = y;
			this.nextValue = nextValue;
			this.type = type;
			this.changeTime = changeTime;
			this.dist = dist;
		}
		@Override
		public int compareTo(Element e) {
			if (this.dist < e.dist) {
				return - 1;
			} else if (this.dist > e.dist) {
				return 1;
			} else {
				if (this.changeTime < e.changeTime) {
					return - 1;
				} else if (this.changeTime > e.changeTime) {
					return 1;
				} else {
					return 0;
				}
			}
		}
	}

	private static final int BISHOP = 0 , KNIGHT = 1 , ROOK = 2;

	private static int[][][][] dists = new int[20][20][110][3];
	private static int[][][][] changes = new int[20][20][110][3];
	private static int[][] values = new int[100][100];

	public static void main(String[] args) {

		Scanner scan = new Scanner(System.in);

		int i , j , k , n = scan.nextInt() , x = - 1 , y = - 1;
		for (i = 0;i < n;i ++) {
			for (j = 0;j < n;j ++) {
				values[i][j] = scan.nextInt();
				if (values[i][j] == 1) {
					x = i;
					y = j;
				}
			}
		}
		for (i = 0;i < n;i ++) {
			for (j = 0;j < n;j ++) {
				for (k = 1;k <= 100;k ++) {
					dists[i][j][k][0] = dists[i][j][k][1] = dists[i][j][k][2] = - 1;
				}
			}
		}
		dists[x][y][1][BISHOP] = 0;
		dists[x][y][1][KNIGHT] = 0;
		dists[x][y][1][ROOK] = 0;

		PriorityQueue<Element> queue = new PriorityQueue<>();
		queue.add(new Element(x , y , 1 , BISHOP , 0 , 0));
		queue.add(new Element(x , y , 1 , KNIGHT , 0 , 0));
		queue.add(new Element(x , y , 1 , ROOK , 0 , 0));

		while (!queue.isEmpty()) {
			Element e = queue.poll();
			if (dists[e.x][e.y][e.nextValue][e.type] == e.dist) {
				int nextValue = e.nextValue;
				if (e.nextValue == values[e.x][e.y]) {
					if (e.nextValue == n * n) {
						System.out.println(e.dist + " " + e.changeTime);
						break;
					} else {
						nextValue ++;
					}
				}
				for (i = 0;i < 3;i ++) {
					int nextChangeTime = e.changeTime;
					int nextType = i;
					int nextDist = e.dist + 1;
					if (i != e.type) {
						nextChangeTime ++;
						nextDist ++;
					}
					if (i == BISHOP) {
						for (int dx = - 1;dx <= 1;dx += 2) {
							for (int dy = - 1;dy <= 1;dy += 2) {
								int tx = e.x + dx , ty = e.y + dy;
								while (tx >= 0 && tx < n && ty >= 0 && ty < n) {
									if (nextDist < dists[tx][ty][nextValue][nextType] || dists[tx][ty][nextValue][nextType] < 0) {
										dists[tx][ty][nextValue][nextType] = nextDist;
										changes[tx][ty][nextValue][nextType] = nextChangeTime;
										queue.add(new Element(tx , ty , nextValue , nextType, nextChangeTime , nextDist));
									} else if (nextDist == dists[tx][ty][nextValue][nextType]) {
										if (nextChangeTime < changes[tx][ty][nextValue][nextType]) {
											changes[tx][ty][nextValue][nextType] = nextChangeTime;
											queue.add(new Element(tx , ty , nextValue , nextType, nextChangeTime , nextDist));
										}
									}
									tx += dx;
									ty += dy;
								}
							}
						}
					} else if (i == KNIGHT) {
						for (int d = 0;d < 8;d ++) {
							int tx = e.x + knightDx[d];
							int ty = e.y + knightDy[d];
							if (tx >= 0 && tx < n && ty >= 0 && ty < n) {
								if (nextDist < dists[tx][ty][nextValue][nextType] || dists[tx][ty][nextValue][nextType] < 0) {
									dists[tx][ty][nextValue][nextType] = nextDist;
									changes[tx][ty][nextValue][nextType] = nextChangeTime;
									queue.add(new Element(tx , ty , nextValue , nextType, nextChangeTime , nextDist));
								} else if (nextDist == dists[tx][ty][nextValue][nextType]) {
									if (nextChangeTime < changes[tx][ty][nextValue][nextType]) {
										changes[tx][ty][nextValue][nextType] = nextChangeTime;
										queue.add(new Element(tx , ty , nextValue , nextType, nextChangeTime , nextDist));
									}
								}
							}
						}
					} else {
						// ROOK
						int tx = e.x , ty = e.y - 1;
						// left
						while (tx >= 0 && tx < n && ty >= 0 && ty < n) {
							if (nextDist < dists[tx][ty][nextValue][nextType] || dists[tx][ty][nextValue][nextType] < 0) {
								dists[tx][ty][nextValue][nextType] = nextDist;
								changes[tx][ty][nextValue][nextType] = nextChangeTime;
								queue.add(new Element(tx , ty , nextValue , nextType, nextChangeTime , nextDist));
							} else if (nextDist == dists[tx][ty][nextValue][nextType]) {
								if (nextChangeTime < changes[tx][ty][nextValue][nextType]) {
									changes[tx][ty][nextValue][nextType] = nextChangeTime;
									queue.add(new Element(tx , ty , nextValue , nextType, nextChangeTime , nextDist));
								}
							}
							ty --;
						}
						// right
						tx = e.x;
						ty = e.y + 1;
						while (tx >= 0 && tx < n && ty >= 0 && ty < n) {
							if (nextDist < dists[tx][ty][nextValue][nextType] || dists[tx][ty][nextValue][nextType] < 0) {
								dists[tx][ty][nextValue][nextType] = nextDist;
								changes[tx][ty][nextValue][nextType] = nextChangeTime;
								queue.add(new Element(tx , ty , nextValue , nextType, nextChangeTime , nextDist));
							} else if (nextDist == dists[tx][ty][nextValue][nextType]) {
								if (nextChangeTime < changes[tx][ty][nextValue][nextType]) {
									changes[tx][ty][nextValue][nextType] = nextChangeTime;
									queue.add(new Element(tx , ty , nextValue , nextType, nextChangeTime , nextDist));
								}
							}
							ty ++;
						}
						// up
						tx = e.x - 1;
						ty = e.y;
						while (tx >= 0 && tx < n && ty >= 0 && ty < n) {
							if (nextDist < dists[tx][ty][nextValue][nextType] || dists[tx][ty][nextValue][nextType] < 0) {
								dists[tx][ty][nextValue][nextType] = nextDist;
								changes[tx][ty][nextValue][nextType] = nextChangeTime;
								queue.add(new Element(tx , ty , nextValue , nextType, nextChangeTime , nextDist));
							} else if (nextDist == dists[tx][ty][nextValue][nextType]) {
								if (nextChangeTime < changes[tx][ty][nextValue][nextType]) {
									changes[tx][ty][nextValue][nextType] = nextChangeTime;
									queue.add(new Element(tx , ty , nextValue , nextType, nextChangeTime , nextDist));
								}
							}
							tx --;
						}
						// down
						tx = e.x + 1;
						ty = e.y;
						while (tx >= 0 && tx < n && ty >= 0 && ty < n) {
							if (nextDist < dists[tx][ty][nextValue][nextType] || dists[tx][ty][nextValue][nextType] < 0) {
								dists[tx][ty][nextValue][nextType] = nextDist;
								changes[tx][ty][nextValue][nextType] = nextChangeTime;
								queue.add(new Element(tx , ty , nextValue , nextType, nextChangeTime , nextDist));
							} else if (nextDist == dists[tx][ty][nextValue][nextType]) {
								if (nextChangeTime < changes[tx][ty][nextValue][nextType]) {
									changes[tx][ty][nextValue][nextType] = nextChangeTime;
									queue.add(new Element(tx , ty , nextValue , nextType, nextChangeTime , nextDist));
								}
							}
							tx ++;
						}
					}
				}
			}
		}

	}

	private static int[] knightDx = new int[] {- 2 , - 1 , 1 , 2 , 2 , 1 , - 1 , - 2};
	private static int[] knightDy = new int[] {1 , 2 , 2 , 1 , - 1 , - 2 , - 2 , - 1};

}

import java.io.*;
import java.util.*;

public class CF1065D {
	static final int INF = Integer.MAX_VALUE, K = 0, B = 1, R = 2;
	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		int n = Integer.parseInt(br.readLine());
		int[] ij = new int[n * n];
		for (int i = 0; i < n; i++) {
			StringTokenizer st = new StringTokenizer(br.readLine());
			for (int j = 0; j < n; j++) {
				int a = Integer.parseInt(st.nextToken()) - 1;
				ij[a] = i * n + j;
			}
		}
		int[][][][][][] ss = new int[n][n][n][n][3][3];
		int[][][][][][] rr = new int[n][n][n][n][3][3];
		for (int i = 0; i < n; i++)
			for (int j = 0; j < n; j++)
				for (int i_ = 0; i_ < n; i_++)
					for (int j_ = 0; j_ < n; j_++)
						for (int u = 0; u < 3; u++)
							for (int v = 0; v < 3; v++)
								ss[i][j][i_][j_][u][v] = INF;
		for (int i = 0; i < n; i++)
			for (int j = 0; j < n; j++)
				for (int i_ = 0; i_ < n; i_++)
					for (int j_ = 0; j_ < n; j_++)
						if (i == i_ && j == j_)
							for (int u = 0; u < 3; u++)
								for (int v = 0; v < 3; v++)
									ss[i][j][i][j][u][v] = rr[i][j][i][j][u][v] = u == v ? 0 : 1;
						else {
							int di = Math.abs(i - i_);
							int dj = Math.abs(j - j_);
							if (di == 2 && dj == 1 || di == 1 && dj == 2)
								ss[i][j][i_][j_][K][K] = 1;
							if (di == 0 || dj == 0)
								ss[i][j][i_][j_][R][R] = 1;
							if (i + j == i_ + j_ || i - j == i_ - j_)
								ss[i][j][i_][j_][B][B] = 1;
						}
		for (int z = 0; z < n * n; z++) {
			int iz = z / n, jz = z % n;
			for (int w = 0; w < 3; w++)
				for (int x = 0; x < n * n; x++) {
					int ix = x / n, jx = x % n;
					for (int u = 0; u < 3; u++) {
						int sxz = ss[ix][jx][iz][jz][u][w];
						int rxz = rr[ix][jx][iz][jz][u][w];
						if (sxz == INF)
							continue;
						for (int y = 0; y < n * n; y++) {
							int iy = y / n, jy = y % n;
							for (int v = 0; v < 3; v++) {
								int szy = ss[iz][jz][iy][jy][w][v];
								int rzy = rr[iz][jz][iy][jy][w][v];
								if (szy == INF)
									continue;
								int sxy = ss[ix][jx][iy][jy][u][v];
								int rxy = rr[ix][jx][iy][jy][u][v];
								if (sxy > sxz + szy || sxy == sxz + szy && rxy > rxz + rzy) {
									ss[ix][jx][iy][jy][u][v] = sxz + szy;
									rr[ix][jx][iy][jy][u][v] = rxz + rzy;
								}
							}
						}
					}
				}
		}
		int[] ps = new int[3], pr = new int[3];
		int[] qs = new int[3], qr = new int[3], tmp;
		int r, s;
		for (int h = 1; h < n * n; h++) {
			int i = ij[h - 1] / n, j = ij[h - 1] % n, i_ = ij[h] / n, j_ = ij[h] % n;
			for (int v = 0; v < 3; v++) {
				s = INF; r = 0;
				for (int u = 0; u < 3; u++) {
					int s0 = ps[u], s1 = ss[i][j][i_][j_][u][v];
					int s_ = s0 == INF || s1 == INF ? INF : s0 + s1;
					int r_ = pr[u] + rr[i][j][i_][j_][u][v];
					if (s > s_ || s == s_ && r > r_) {
						s = s_; r = r_;
					}
				}
				qs[v] = s; qr[v] = r;
			}
			tmp = ps; ps = qs; qs = tmp;
			tmp = pr; pr = qr; qr = tmp;
		}
		s = INF; r = 0;
		for (int v = 0; v < 3; v++) {
			int s_ = ps[v], r_ = pr[v];
			if (s > s_ || s == s_ && r > r_) {
				s = s_; r = r_;
			}
		}
		System.out.println(s + " " + r);
	}
}

import java.io.OutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.IOException;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.InputStream;

/**
 * Built using CHelper plug-in
 * Actual solution is at the top
 *
 * @author Vadim
 */
public class Main {
    public static void main(String[] args) {
        InputStream inputStream = System.in;
        OutputStream outputStream = System.out;
        InputReader in = new InputReader(inputStream);
        PrintWriter out = new PrintWriter(outputStream);
        TaskD solver = new TaskD();
        solver.solve(1, in, out);
        out.close();
    }

    static class TaskD {
        public static final int MAX_PATH = 1000000;
        public static final int K = 0;
        public static final int B = 1;
        public static final int R = 2;
        Point[] coords;
        int[][] d;
        int[] di = new int[]{2, 2, 1, -1, -2, -2, 1, -1};
        int[] dj = new int[]{-1, 1, 2, 2, 1, -1, -2, -2};
        int n;

        int getBDist(int r, int c, int ra, int ca) {
            if (r == ra && c == ca)
                return 0;
            if ((r + c) % 2 != (ra + ca) % 2)
                return MAX_PATH;
            if (r - ra == c - ca || r - ra == ca - c)
                return 1;
            return 2;
        }

        int getRDist(int r, int c, int ra, int ca) {
            if (r == ra && c == ca)
                return 0;
            if (r == ra || c == ca)
                return 1;
            return 2;
        }

        Pair dist(int from, int to, int start, int end) {
            Point p = coords[from];
            Point q = coords[to];
            if (start == end) {
                if (start == K)
                    return new Pair(d[from][to], 0);
                else if (start == B) {
                    return new Pair(getBDist(p.r, p.c, q.r, q.c), 0);//todo:
                } else
                    return new Pair(getRDist(p.r, p.c, q.r, q.c), 0);
            } else {
                if (start == R && end == B || start == B && end == R) {
                    if (getBDist(p.r, p.c, q.r, q.c) == 1)
                        return new Pair(2, 1);
                    if (getRDist(p.r, p.c, q.r, q.c) == 1)
                        return new Pair(2, 1);
                    return new Pair(3, 1);
                }
                if (start == K && end == B || start == B && end == K) {
                    if (end == K) {
                        Point t = p;
                        p = q;
                        q = t;
                    }
                    int min = getBDist(p.r, p.c, q.r, q.c);
                    for (int i = 0; i < 8; i++) {
                        int ni = p.r + di[i];
                        int nj = p.c + dj[i];
                        if (0 <= ni && ni < n && 0 <= nj && nj < n) {
                            min = Math.min(1 + getBDist(ni, nj, q.r, q.c), min);
                        }
                    }
                    return new Pair(min + 1, 1);
                }
                if (start == K && end == R || start == R && end == K) {
                    if (end == K) {
                        Point t = p;
                        p = q;
                        q = t;
                    }
                    int min = getRDist(p.r, p.c, q.r, q.c);
                    for (int i = 0; i < 8; i++) {
                        int ni = p.r + di[i];
                        int nj = p.c + dj[i];
                        if (0 <= ni && ni < n && 0 <= nj && nj < n) {
                            min = Math.min(1 + getRDist(ni, nj, q.r, q.c), min);
                        }
                    }
                    return new Pair(min + 1, 1);
                }
            }
            throw new RuntimeException();

        }

        public void solve(int testNumber, InputReader in, PrintWriter out) {

            n = in.ni();
            int[][] a = new int[n][n];
            for (int i = 0; i < n; i++) {
                for (int j = 0; j < n; j++) {
                    a[i][j] = in.ni() - 1;
                }
            }
            int n2 = n * n;

            d = new int[n2][n2];
            for (int i = 0; i < n2; i++) {
                for (int j = 0; j < n2; j++) {
                    if (i == j)
                        d[i][i] = 0;
                    else
                        d[i][j] = MAX_PATH;
                }
            }

            coords = new Point[n2];
            for (int i = 0; i < n; i++) {
                for (int j = 0; j < n; j++) {
                    coords[a[i][j]] = new Point(i, j);
                    for (int k = 0; k < 8; k++) {
                        int ni = i + di[k];
                        int nj = j + dj[k];
                        if (0 <= ni && ni < n && 0 <= nj && nj < n) {
                            d[a[i][j]][a[ni][nj]] = 1;
                        }
                    }

                }
            }
            for (int k = 0; k < n2; k++) {
                for (int i = 0; i < n2; i++) {
                    for (int j = 0; j < n2; j++) {
                        d[i][j] = Math.min(d[i][j], d[i][k] + d[k][j]);
                    }
                }
            }
            int ans[][] = new int[n2][3];
            int swaps[][] = new int[n2][3];
            for (int i = 0; i < n2 - 1; i++) {
                for (int j = 0; j < 3; j++) {
                    int minDist = MAX_PATH;
                    int minSwap = n2;
                    for (int k = 0; k < 3; k++) {
                        Pair pair = dist(i, i + 1, k, j);
                        int dist = ans[i][k] + pair.x;
                        int swap = swaps[i][k] + pair.y;
                        if (dist < minDist || dist == minDist && swap < minSwap) {
                            minDist = dist;
                            minSwap = swap;
                        }
                    }
                    ans[i + 1][j] += minDist;
                    swaps[i + 1][j] = minSwap;
                }
            }
            int minDist = MAX_PATH;
            int minSwap = n2;
            for (int k = 0; k < 3; k++) {
                int dist = ans[n2 - 1][k];
                int swap = swaps[n2 - 1][k];
                if (dist < minDist || dist == minDist && swap < minSwap) {
                    minDist = dist;
                    minSwap = swap;
                }
            }

            out.println(minDist + " " + minSwap);

        }

        class Point {
            int r;
            int c;

            public Point(int r, int c) {
                this.r = r;
                this.c = c;
            }

        }

        class Pair {
            int x;
            int y;

            public Pair(int x, int y) {
                this.x = x;
                this.y = y;
            }

        }

    }

    static class InputReader extends BufferedReader {
        public InputReader(InputStream st) {
            super(new InputStreamReader(st));
        }

        public String readLine() {
            try {
                return super.readLine();
            } catch (IOException e) {
                return null;
            }
        }

        private int readByte() {
            try {
                return read();
            } catch (IOException e) {
                throw new RuntimeException();
            }
        }

        public int ni() {
            int num = 0, b;
            boolean minus = false;
            while ((b = readByte()) != -1 && !((b >= '0' && b <= '9') || b == '-')) ;
            if (b == '-') {
                minus = true;
                b = readByte();
            }

            while (true) {
                if (b >= '0' && b <= '9') {
                    num = num * 10 + (b - '0');
                } else {
                    return minus ? -num : num;
                }
                b = readByte();
            }
        }

    }
}

import java.io.*;
import java.util.*;

public class D {

    static int add=(int)1e5;

    public static void main(String[] args) throws Throwable {
        MyScanner sc = new MyScanner();
        PrintWriter pw = new PrintWriter(System.out);

        n=sc.nextInt();
        int [][] a=new int [n][n];
        int [] x=new int [n*n+1];
        int [] y=new int [n*n+1];
        for(int i=0;i<n;i++)
            for(int j=0;j<n;j++){
                a[i][j]=sc.nextInt();
                x[a[i][j]]=i;
                y[a[i][j]]=j;
            }
        dis=new long [n*n+1][3][3];
        for(int i=1;i<n*n;i++){
            for(int p=0;p<3;p++){
                long [][] tmpDis=new long [n*n+1][3];
                for(long [] xx : tmpDis)
                    Arrays.fill(xx,(long)2e18);
                Queue<pair> q=new LinkedList<>();
                q.add(new pair(x[i],y[i],p));
                tmpDis[i][p]=0;
                while(!q.isEmpty()){
                    pair cur=q.poll();
                    int num=a[cur.x][cur.y];
                    for(int j=0;j<3;j++)
                        if(j!=cur.p && tmpDis[num][j]>tmpDis[num][cur.p]+add+1){
                            tmpDis[num][j]=tmpDis[num][cur.p]+add+1;
                            q.add(new pair(cur.x,cur.y,j));
                        }
                    if(cur.p==0){ // knight
                        for(int j=0;j<8;j++){
                            int x2=cur.x+dx[j];
                            int y2=cur.y+dy[j];
                            if(valid(x2,y2) && tmpDis[a[x2][y2]][0]>tmpDis[num][0]+add){
                                tmpDis[a[x2][y2]][0]=tmpDis[num][0]+add;
                                q.add(new pair(x2,y2,0));
                            }
                        }
                    }else
                        if(cur.p==1){ // rock
                            for(int j=0;j<n;j++){
                                if(j==cur.x)
                                    continue;
                                int x2=j;
                                int y2=cur.y;
                                if(valid(x2,y2) && tmpDis[a[x2][y2]][1]>tmpDis[num][1]+add){
                                    tmpDis[a[x2][y2]][1]=tmpDis[num][1]+add;
                                    q.add(new pair(x2,y2,1));
                                }
                            }
                            for(int j=0;j<n;j++){
                                if(j==cur.y)
                                    continue;
                                int x2=cur.x;
                                int y2=j;
                                if(valid(x2,y2) && tmpDis[a[x2][y2]][1]>tmpDis[num][1]+add){
                                    tmpDis[a[x2][y2]][1]=tmpDis[num][1]+add;
                                    q.add(new pair(x2,y2,1));
                                }
                            }
                        }else{
                            for(int j=1;j<10;j++){
                                int x2=cur.x+j;
                                int y2=cur.y+j;
                                if(valid(x2,y2) && tmpDis[a[x2][y2]][2]>tmpDis[num][2]+add){
                                    tmpDis[a[x2][y2]][2]=tmpDis[num][2]+add;
                                    q.add(new pair(x2,y2,2));
                                }

                                x2=cur.x+j;
                                y2=cur.y-j;
                                if(valid(x2,y2) && tmpDis[a[x2][y2]][2]>tmpDis[num][2]+add){
                                    tmpDis[a[x2][y2]][2]=tmpDis[num][2]+add;
                                    q.add(new pair(x2,y2,2));
                                }

                                x2=cur.x-j;
                                y2=cur.y-j;
                                if(valid(x2,y2) && tmpDis[a[x2][y2]][2]>tmpDis[num][2]+add){
                                    tmpDis[a[x2][y2]][2]=tmpDis[num][2]+add;
                                    q.add(new pair(x2,y2,2));
                                }

                                x2=cur.x-j;
                                y2=cur.y+j;
                                if(valid(x2,y2) && tmpDis[a[x2][y2]][2]>tmpDis[num][2]+add){
                                    tmpDis[a[x2][y2]][2]=tmpDis[num][2]+add;
                                    q.add(new pair(x2,y2,2));
                                }

                            }
                        }

                }
                for(int p2=0;p2<3;p2++)
                    dis[i+1][p][p2]=tmpDis[i+1][p2];
            }
        }

        mem=new Long[n*n+1][4];
        long ans=Math.min(dp(1,0),Math.min(dp(1,1),dp(1,2)));
        pw.println(ans/add+" "+ans%add);

        pw.flush();
        pw.close();
    }

    static Long [][] mem;

    static long dp(int i,int p){
        if(i==n*n)
            return 0;
        if(mem[i][p]!=null)
            return mem[i][p];
        long ret=(long)2e18;
        for(int p2=0;p2<3;p2++)
            ret=Math.min(ret,dis[i+1][p][p2]+dp(i+1,p2));
        return mem[i][p]=ret;
    }

    static long [][][] dis;

    static int [] dx={1,1,2,2,-1,-1,-2,-2};
    static int [] dy={2,-2,1,-1,2,-2,1,-1};

    static int n;
    static boolean valid(int i,int j){
        return Math.min(i,j)>=0 && Math.max(i,j)<n;
    }

    static class pair{
        int x,y,p;
        pair(int a,int b,int c){
            x=a;
            y=b;
            p=c;
        }
    }

    static class MyScanner {
        BufferedReader br;
        StringTokenizer st;

        public MyScanner() {
            br = new BufferedReader(new InputStreamReader(System.in));
        }

        String next() {
            while (st == null || !st.hasMoreElements()) {
                try {
                    st = new StringTokenizer(br.readLine());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return st.nextToken();
        }

        int nextInt() {
            return Integer.parseInt(next());
        }

        long nextLong() {
            return Long.parseLong(next());
        }

        double nextDouble() {
            return Double.parseDouble(next());
        }

        String nextLine() {
            String str = "";
            try {
                str = br.readLine();
            } catch (IOException e) {
                e.printStackTrace();
            }
            return str;
        }
    }
}

import java.io.OutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.StringTokenizer;
import java.io.IOException;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.InputStream;

/**
 * Built using CHelper plug-in
 * Actual solution is at the top
 *
 * @author dmytro.prytula
 */
public class Main {
    public static void main(String[] args) {
        InputStream inputStream = System.in;
        OutputStream outputStream = System.out;
        InputReader in = new InputReader(inputStream);
        PrintWriter out = new PrintWriter(outputStream);
        DTriFiguri solver = new DTriFiguri();
        solver.solve(1, in, out);
        out.close();
    }

    static class DTriFiguri {
        static int N;
        int M = 305;
        int INF = (int) 1e9;
        DTriFiguri.Point[] possibleKnight = new DTriFiguri.Point[]{
                DTriFiguri.Point.of(2, 1),
                DTriFiguri.Point.of(-2, 1),
                DTriFiguri.Point.of(2, -1),
                DTriFiguri.Point.of(-2, -1),
                DTriFiguri.Point.of(1, 2),
                DTriFiguri.Point.of(-1, 2),
                DTriFiguri.Point.of(1, -2),
                DTriFiguri.Point.of(-1, -2)
        };
        DTriFiguri.Point[][] dist = new DTriFiguri.Point[M][M];

        {

        }

        private boolean in(DTriFiguri.Point p) {
            return 0 <= p.x && p.x < N && 0 <= p.y && p.y < N;
        }

        public void solve(int testNumber, InputReader in, PrintWriter out) {

            N = in.nextInt();
            DTriFiguri.Point[] pos = new DTriFiguri.Point[N * N];
            int[][] A = new int[N][N];
            for (int x = 0; x < N; x++) {
                for (int y = 0; y < N; y++) {
                    A[x][y] = in.nextInt() - 1;
                    pos[A[x][y]] = DTriFiguri.Point.of(x, y);

                }
            }

            for (int i = 0; i < M; i++) {
                for (int j = 0; j < M; j++) {
//                Arrays.fill(dist[i], Point.of(INF, INF));
                    dist[i][j] = DTriFiguri.Point.of(INF, INF);
                }
            }
            for (int i = 0; i < M; i++)
//            for (int j = 0; j < M; j++)
//                if (i == j)
                dist[i][i] = DTriFiguri.Point.of(0, 0);

            for (int x = 0; x < N; x++) {
                for (int y = 0; y < N; y++) {
                    //knight
                    for (int i = 0; i < possibleKnight.length; i++) {
                        int nx = x + possibleKnight[i].x;
                        int ny = y + possibleKnight[i].y;
                        if (in(DTriFiguri.Point.of(nx, ny))) {
                            dist[DTriFiguri.Point.hash(x, y, 0)][DTriFiguri.Point.hash(nx, ny, 0)] = DTriFiguri.Point.of(1, 0);
                        }
                    }
                    //bishop
                    for (int i = -N + 1; i <= N - 1; i++) {
                        int nx = x + i;
                        int ny = y + i;
                        if (in(DTriFiguri.Point.of(nx, ny)))
                            dist[DTriFiguri.Point.hash(x, y, 1)][DTriFiguri.Point.hash(nx, ny, 1)] = DTriFiguri.Point.of(1, 0);

                        ny = y - i;
                        if (in(DTriFiguri.Point.of(nx, ny)))
                            dist[DTriFiguri.Point.hash(x, y, 1)][DTriFiguri.Point.hash(nx, ny, 1)] = DTriFiguri.Point.of(1, 0);
                    }

                    //rook
                    for (int i = 0; i < N; i++) {
                        int nx = i;
                        int ny = y;
//                    if (in(Point.of(nx, ny)))
                        dist[DTriFiguri.Point.hash(x, y, 2)][DTriFiguri.Point.hash(nx, ny, 2)] = DTriFiguri.Point.of(1, 0);
                        nx = x;
                        ny = i;
//                    if (in(Point.of(nx, ny)))
                        dist[DTriFiguri.Point.hash(x, y, 2)][DTriFiguri.Point.hash(nx, ny, 2)] = DTriFiguri.Point.of(1, 0);

                    }

                    for (int i = 0; i < 3; i++) {
                        for (int j = 0; j < 3; j++) {
                            if (i != j) {
                                dist[DTriFiguri.Point.hash(x, y, i)][DTriFiguri.Point.hash(x, y, j)] = DTriFiguri.Point.of(1, 1);
                            }
                        }
                    }

                }
            }

            for (int k = 0; k < M; k++) {
                for (int i = 0; i < M; i++) {
                    for (int j = 0; j < M; j++) {
                        dist[i][j] = DTriFiguri.Point.min(dist[i][j], DTriFiguri.Point.sum(dist[i][k], dist[k][j]));
                    }
                }
            }

            DTriFiguri.Point[][] dp = new DTriFiguri.Point[N * N][3];
            for (int i = 0; i < N * N; i++) {
                for (int j = 0; j < 3; j++) {
                    dp[i][j] = DTriFiguri.Point.of(INF, INF);
                }
            }

            dp[0][0] = dp[0][1] = dp[0][2] = DTriFiguri.Point.of(0, 0);

            for (int i = 0; i < N * N - 1; i++)
                for (int j = 0; j < 3; j++)
                    for (int k = 0; k < 3; k++)
                        dp[i + 1][k] = DTriFiguri.Point.min(
                                dp[i + 1][k],
                                DTriFiguri.Point.sum(dp[i][j], dist[DTriFiguri.Point.hash(pos[i].x, pos[i].y, j)][DTriFiguri.Point.hash(pos[i + 1].x, pos[i + 1].y, k)])
                        );

            DTriFiguri.Point ans = DTriFiguri.Point.of(INF, INF);
            ans = DTriFiguri.Point.min(ans, dp[N * N - 1][0]);
            ans = DTriFiguri.Point.min(ans, dp[N * N - 1][1]);
            ans = DTriFiguri.Point.min(ans, dp[N * N - 1][2]);

            out.print(ans.toString());
        }

        private static class Point {
            int x;
            int y;

            private Point(int x, int y) {
                this.x = x;
                this.y = y;
            }

            public static DTriFiguri.Point of(int x, int y) {
                return new DTriFiguri.Point(x, y);
            }

            public static int hash(int x, int y, int p) {
                return x * N * 3 + y * 3 + p;
            }

            public static DTriFiguri.Point min(DTriFiguri.Point a, DTriFiguri.Point b) {
                if (a.x == b.x) {
                    return a.y < b.y ? a : b;
                }
                return a.x < b.x ? a : b;
            }

            public static DTriFiguri.Point sum(DTriFiguri.Point point1, DTriFiguri.Point point) {
                return DTriFiguri.Point.of(point1.x + point.x, point1.y + point.y);
            }

            public String toString() {
                return x + " " + y;
            }

        }

    }

    static class InputReader {
        public BufferedReader reader;
        public StringTokenizer tokenizer;

        public InputReader(InputStream stream) {
            reader = new BufferedReader(new InputStreamReader(stream), 32768);
            tokenizer = null;
        }

        public String nextToken() {
            while (tokenizer == null || !tokenizer.hasMoreTokens()) {
                try {
                    tokenizer = new StringTokenizer(reader.readLine());
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
            return tokenizer.nextToken();
        }

        public int nextInt() {
            return Integer.parseInt(nextToken());
        }

    }
}

import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.*;

public class d{

	static class Pos{
		int i,j;
		Pos(int i, int j){this.i=i;this.j=j;}
	}
	static class Node{
		int i,j,k,l,c;
		Node(int i,int j,int k,int l,int c){this.i=i;this.j=j;this.k=k;this.l=l;this.c=c;}
	}

	static void solve(){
		int n = ni();
		int[][][][] dis = new int[n][n][3][n*n+1];
		Pos[] pos =new Pos[n*n];
		for(int i=0;i<n;++i)for(int j=0;j<n;++j)pos[ni()-1] = new Pos(i, j);
		for(int i=0;i<n;++i)for(int j=0;j<n;++j)for(int k=0;k<3;++k)for(int l=0;l<=n*n;++l)dis[i][j][k][l]=Integer.MAX_VALUE;
		PriorityQueue<Node> que = new PriorityQueue<>((a,b)->a.c-b.c);
		for(int k=0;k<3;++k){
			dis[pos[0].i][pos[0].j][k][1]=0;
			que.add(new Node(pos[0].i, pos[0].j, k, 1, 0));
		}
		List<Integer> movex[] = new List[3];
		List<Integer> movey[] = new List[3];
		movex[0] = Arrays.asList(-2,-2,-1,-1,1,1,2,2);
		movey[0] = Arrays.asList(1,-1,2,-2,2,-2,1,-1);
		for(int i=1;i<3;++i){
			movex[i]=new ArrayList<>();
			movey[i]=new ArrayList<>();
		}
		for(int i=1;i<=n;++i){
			movex[1].add(i);movex[1].add(-i);
			movey[1].add(0);movey[1].add(0);
			movex[1].add(0);movex[1].add(0);
			movey[1].add(i);movey[1].add(-i);

			movex[2].add(i);movex[2].add(-i);
			movey[2].add(i);movey[2].add(-i);
			movex[2].add(i);movex[2].add(-i);
			movey[2].add(-i);movey[2].add(i);
		}
		while(!que.isEmpty()){
			Node node = que.poll();
			if(node.c > dis[node.i][node.j][node.k][node.l])continue;
			int i=node.i, j=node.j, k=node.k, l=node.l;
			if(l==n*n){
				out.println(node.c/1000+" "+node.c%1000);
				return;
			}
			for(int kk=0;kk<3;++kk){
				if(kk==k)continue;
				if(dis[i][j][kk][l] > dis[i][j][k][l]+1001){
					dis[i][j][kk][l]=dis[i][j][k][l]+1001;
					que.add(new Node(i, j, kk, l, dis[i][j][kk][l]));
				}
			}
			for(int x=0;x<movex[k].size();++x){
				int dx = movex[k].get(x);
				int dy = movey[k].get(x);

				if(inrange(i+dy, j+dx, n, n)){
					int ll = l;
					if(pos[l].i==i+dy && pos[l].j==j+dx)++ll;
					if(dis[i+dy][j+dx][k][ll]>dis[i][j][k][l]+1000){
						dis[i+dy][j+dx][k][ll]=dis[i][j][k][l]+1000;
						que.add(new Node(i+dy, j+dx, k, ll, dis[i+dy][j+dx][k][ll]));
					}
				}
			}
		}

	}

	 public static void main(String[] args){
		 solve();
		 out.flush();
	 }
	 private static InputStream in = System.in;
	 private static PrintWriter out = new PrintWriter(System.out);

	 static boolean inrange(int y, int x, int h, int w){
		 return y>=0 && y<h && x>=0 && x<w;
	 }
	 @SuppressWarnings("unchecked")
	 static<T extends Comparable> int lower_bound(List<T> list, T key){
		 int lower=-1;int upper=list.size();
		 while(upper - lower>1){
		 int center =(upper+lower)/2;
		 if(list.get(center).compareTo(key)>=0)upper=center;
		 else lower=center;
		 }
		 return upper;
	 }
	 @SuppressWarnings("unchecked")
	 static <T extends Comparable> int upper_bound(List<T> list, T key){
		 int lower=-1;int upper=list.size();
		 while(upper-lower >1){
		 int center=(upper+lower)/2;
		 if(list.get(center).compareTo(key)>0)upper=center;
		 else lower=center;
		 }
		 return upper;
	 }
	 @SuppressWarnings("unchecked")
	 static <T extends Comparable> boolean next_permutation(List<T> list){
		 int lastIndex = list.size()-2;
		 while(lastIndex>=0 && list.get(lastIndex).compareTo(list.get(lastIndex+1))>=0)--lastIndex;
		 if(lastIndex<0)return false;
		 int swapIndex = list.size()-1;
		 while(list.get(lastIndex).compareTo(list.get(swapIndex))>=0)swapIndex--;
		 T tmp = list.get(lastIndex);
		 list.set(lastIndex++, list.get(swapIndex));
		 list.set(swapIndex, tmp);
		 swapIndex = list.size()-1;
		 while(lastIndex<swapIndex){
		 tmp = list.get(lastIndex);
		 list.set(lastIndex, list.get(swapIndex));
		 list.set(swapIndex, tmp);
		 ++lastIndex;--swapIndex;
		 }
		 return true;
	 }
	 private static final byte[] buffer = new byte[1<<15];
	 private static int ptr = 0;
	 private static int buflen = 0;
	 private static boolean hasNextByte(){
		 if(ptr<buflen)return true;
		 ptr = 0;
		 try{
			 buflen = in.read(buffer);
		 } catch (IOException e){
			 e.printStackTrace();
		 }
		 return buflen>0;
	 }
	 private static int readByte(){ if(hasNextByte()) return buffer[ptr++]; else return -1;}
	 private static boolean isSpaceChar(int c){ return !(33<=c && c<=126);}
	 private static int skip(){int res; while((res=readByte())!=-1 && isSpaceChar(res)); return res;}

	 private static double nd(){ return Double.parseDouble(ns()); }
	 private static char nc(){ return (char)skip(); }
	 private static String ns(){
		 StringBuilder sb = new StringBuilder();
		 for(int b=skip();!isSpaceChar(b);b=readByte())sb.append((char)b);
		 return sb.toString();
	 }
	 private static int[] nia(int n){
		 int[] res = new int[n];
		 for(int i=0;i<n;++i)res[i]=ni();
		 return res;
	 }
	 private static long[] nla(int n){
		 long[] res = new long[n];
		 for(int i=0;i<n;++i)res[i]=nl();
		 return res;
	 }
	 private static int ni(){
		 int res=0,b;
		 boolean minus=false;
		 while((b=readByte())!=-1 && !((b>='0'&&b<='9') || b=='-'));
		 if(b=='-'){
			 minus=true;
			 b=readByte();
		 }
		 for(;'0'<=b&&b<='9';b=readByte())res=res*10+(b-'0');
		 return minus ? -res:res;
	 }
	 private static long nl(){
		 long res=0,b;
		 boolean minus=false;
		 while((b=readByte())!=-1 && !((b>='0'&&b<='9') || b=='-'));
		 if(b=='-'){
			 minus=true;
			 b=readByte();
		 }
		 for(;'0'<=b&&b<='9';b=readByte())res=res*10+(b-'0');
		 return minus ? -res:res;
	}
}

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StreamTokenizer;

public class Main {

    static int n;
    static int[] x;
    static int[] y;

    // 0代表马, 1代表象, 2代表车
    public static void main(String[] args) throws IOException {

        StreamTokenizer streamTokenizer = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
        streamTokenizer.nextToken();
        n = (int) streamTokenizer.nval;
        x = new int[n * n + 1];
        y = new int[n * n + 1];
        int[][] f = new int[3][n * n + 1];
        int[][] g = new int[3][n * n + 1];
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= n; j++) {
                streamTokenizer.nextToken();
                x[(int) streamTokenizer.nval] = i;
                y[(int) streamTokenizer.nval] = j;
            }
        f[0][1] = 0;
        f[1][1] = 0;
        f[2][1] = 0;
        g[0][1] = 0;
        g[1][1] = 0;
        g[2][1] = 0;
        for (int i = 2; i <= n * n; i++) {
            for (int j = 0; j < 3; j++) {
                getMinStep(x[i - 1], y[i - 1], 0, x[i], y[i], j);
                f[j][i] = f[0][i - 1] + minStep;
                g[j][i] = g[0][i - 1] + minMove;
                getMinStep(x[i - 1], y[i - 1], 1, x[i], y[i], j);
                if ((f[1][i - 1] + minStep < f[j][i]) || ((f[1][i - 1] + minStep == f[j][i]) && (g[1][i - 1] + minMove < g[j][i]))) {
                    f[j][i] = f[1][i - 1] + minStep;
                    g[j][i] = g[1][i - 1] + minMove;
                }
                getMinStep(x[i - 1], y[i - 1], 2, x[i], y[i], j);
                if ((f[2][i - 1] + minStep < f[j][i]) || ((f[2][i - 1] + minStep == f[j][i]) && (g[2][i - 1] + minMove < g[j][i]))) {
                    f[j][i] = f[2][i - 1] + minStep;
                    g[j][i] = g[2][i - 1] + minMove;
                }
            }
        }
        minStep = f[0][n * n];
        minMove = g[0][n * n];

        if ((f[1][n * n] < minStep) || ((f[1][n * n] == minStep) && (g[1][n * n] < minMove))) {
            minStep = f[1][n * n];
            minMove = g[1][n * n];
        }
        if ((f[2][n * n] < minStep) || ((f[2][n * n] == minStep) && (g[2][n * n] < minMove))) {
            minStep = f[2][n * n];
            minMove = g[2][n * n];
        }
        System.out.print(minStep + " " + minMove);
    }

    static int[] pointX = new int[1000000];
    static int[] pointY = new int[1000000];
    static int[] state = new int[1000000];
    static int[] steps = new int[1000000];
    static int[] moves = new int[1000000];

    static int[][] knight = {
            {-2, -1},
            {-2, 1},
            {-1, -2},
            {-1, 2},
            {1, -2},
            {1, 2},
            {2, -1},
            {2, 1}
    };

    static int minStep;
    static int minMove;

    // 0代表马, 1代表象, 2代表车
    public static void getMinStep(int fromX, int fromY, int fromState, int toX, int toY, int toState) {

        int l = 0, r = 0;
        int tempX, tempY;
        pointX[0] = fromX;
        pointY[0] = fromY;
        state[0] = fromState;
        steps[0] = 0;
        moves[0] = 0;
        while (l <= r) {
            if (state[l] == 0) {
                for (int i = 0; i < knight.length; i++) {
                    tempX = pointX[l] + knight[i][0];
                    tempY = pointY[l] + knight[i][1];
                    if (isInMatrix(tempX, tempY)) {
                        r++;
                        pointX[r] = tempX;
                        pointY[r] = tempY;
                        state[r] = 0;
                        steps[r] = steps[l] + 1;
                        moves[r] = moves[l];
                        if (tempX == toX && tempY == toY && 0 == toState) {
                            minStep = steps[r];
                            minMove = moves[r];
                            return;
                        }
                    }
                }

                r++;
                pointX[r] = pointX[l];
                pointY[r] = pointY[l];
                state[r] = 1;
                steps[r] = steps[l] + 1;
                moves[r] = moves[l] + 1;
                if (pointX[r] == toX && pointY[r] == toY && 1 == toState) {
                    minStep = steps[r];
                    minMove = moves[r];
                    return;
                }

                r++;
                pointX[r] = pointX[l];
                pointY[r] = pointY[l];
                state[r] = 2;
                steps[r] = steps[l] + 1;
                moves[r] = moves[l] + 1;
                if (pointX[r] == toX && pointY[r] == toY && 2 == toState) {
                    minStep = steps[r];
                    minMove = moves[r];
                    return;
                }
            } else if (state[l] == 1) {
                tempX = pointX[l];
                tempY = pointY[l];
                for (; true;) {
                    tempX += 1;
                    tempY += 1;
                    if (isInMatrix(tempX, tempY)) {
                        r++;
                        pointX[r] = tempX;
                        pointY[r] = tempY;
                        state[r] = 1;
                        steps[r] = steps[l] + 1;
                        moves[r] = moves[l];
                        if (tempX == toX && tempY == toY && 1 == toState) {
                            minStep = steps[r];
                            minMove = moves[r];
                            return;
                        }
                    } else
                        break;
                }

                tempX = pointX[l];
                tempY = pointY[l];
                for (; true;) {
                    tempX += 1;
                    tempY += -1;
                    if (isInMatrix(tempX, tempY)) {
                        r++;
                        pointX[r] = tempX;
                        pointY[r] = tempY;
                        state[r] = 1;
                        steps[r] = steps[l] + 1;
                        moves[r] = moves[l];
                        if (tempX == toX && tempY == toY && 1 == toState) {
                            minStep = steps[r];
                            minMove = moves[r];
                            return;
                        }
                    } else
                        break;
                }

                tempX = pointX[l];
                tempY = pointY[l];
                for (; true;) {
                    tempX += -1;
                    tempY += 1;
                    if (isInMatrix(tempX, tempY)) {
                        r++;
                        pointX[r] = tempX;
                        pointY[r] = tempY;
                        state[r] = 1;
                        steps[r] = steps[l] + 1;
                        moves[r] = moves[l];
                        if (tempX == toX && tempY == toY && 1 == toState) {
                            minStep = steps[r];
                            minMove = moves[r];
                            return;
                        }
                    } else
                        break;
                }

                tempX = pointX[l];
                tempY = pointY[l];
                for (; true;) {
                    tempX += -1;
                    tempY += -1;
                    if (isInMatrix(tempX, tempY)) {
                        r++;
                        pointX[r] = tempX;
                        pointY[r] = tempY;
                        state[r] = 1;
                        steps[r] = steps[l] + 1;
                        moves[r] = moves[l];
                        if (tempX == toX && tempY == toY && 1 == toState) {
                            minStep = steps[r];
                            minMove = moves[r];
                            return;
                        }
                    } else
                        break;
                }

                r++;
                pointX[r] = pointX[l];
                pointY[r] = pointY[l];
                state[r] = 0;
                steps[r] = steps[l] + 1;
                moves[r] = moves[l] + 1;
                if (pointX[r] == toX && pointY[r] == toY && 0 == toState) {
                    minStep = steps[r];
                    minMove = moves[r];
                    return;
                }

                r++;
                pointX[r] = pointX[l];
                pointY[r] = pointY[l];
                state[r] = 2;
                steps[r] = steps[l] + 1;
                moves[r] = moves[l] + 1;
                if (pointX[r] == toX && pointY[r] == toY && 2 == toState) {
                    minStep = steps[r];
                    minMove = moves[r];
                    return;
                }

            } else {
                tempX = pointX[l];
                tempY = pointY[l];
                for (; true;) {
                    tempX += 1;
                    if (isInMatrix(tempX, tempY)) {
                        r++;
                        pointX[r] = tempX;
                        pointY[r] = tempY;
                        state[r] = 2;
                        steps[r] = steps[l] + 1;
                        moves[r] = moves[l];
                        if (tempX == toX && tempY == toY && 2 == toState) {
                            minStep = steps[r];
                            minMove = moves[r];
                            return;
                        }
                    } else
                        break;
                }

                tempX = pointX[l];
                tempY = pointY[l];
                for (; true;) {
                    tempX += -1;
                    if (isInMatrix(tempX, tempY)) {
                        r++;
                        pointX[r] = tempX;
                        pointY[r] = tempY;
                        state[r] = 2;
                        steps[r] = steps[l] + 1;
                        moves[r] = moves[l];
                        if (tempX == toX && tempY == toY && 2 == toState) {
                            minStep = steps[r];
                            minMove = moves[r];
                            return;
                        }
                    } else
                        break;
                }

                tempX = pointX[l];
                tempY = pointY[l];
                for (; true;) {
                    tempY += 1;
                    if (isInMatrix(tempX, tempY)) {
                        r++;
                        pointX[r] = tempX;
                        pointY[r] = tempY;
                        state[r] = 2;
                        steps[r] = steps[l] + 1;
                        moves[r] = moves[l];
                        if (tempX == toX && tempY == toY && 2 == toState) {
                            minStep = steps[r];
                            minMove = moves[r];
                            return;
                        }
                    } else
                        break;
                }

                tempX = pointX[l];
                tempY = pointY[l];
                for (; true;) {
                    tempY += -1;
                    if (isInMatrix(tempX, tempY)) {
                        r++;
                        pointX[r] = tempX;
                        pointY[r] = tempY;
                        state[r] = 2;
                        steps[r] = steps[l] + 1;
                        moves[r] = moves[l];
                        if (tempX == toX && tempY == toY && 2 == toState) {
                            minStep = steps[r];
                            minMove = moves[r];
                            return;
                        }
                    } else
                        break;
                }

                r++;
                pointX[r] = pointX[l];
                pointY[r] = pointY[l];
                state[r] = 0;
                steps[r] = steps[l] + 1;
                moves[r] = moves[l] + 1;
                if (pointX[r] == toX && pointY[r] == toY && 0 == toState) {
                    minStep = steps[r];
                    minMove = moves[r];
                    return;
                }

                r++;
                pointX[r] = pointX[l];
                pointY[r] = pointY[l];
                state[r] = 1;
                steps[r] = steps[l] + 1;
                moves[r] = moves[l] + 1;
                if (pointX[r] == toX && pointY[r] == toY && 1 == toState) {
                    minStep = steps[r];
                    minMove = moves[r];
                    return;
                }
            }
            l++;
        }
    }

    public static boolean isInMatrix(int x, int y) {
        if (x >= 1 && x <= n && y >= 1 && y <= n)
            return true;
        return false;
    }

}

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.PriorityQueue;
import java.util.StringTokenizer;

public class D2 {
    static class FastReader {
        BufferedReader br;
        StringTokenizer st;

        public FastReader() {
            br = new BufferedReader(new InputStreamReader(System.in));
        }

        String next() {
            while (st == null || !st.hasMoreElements()) {
                try {
                    st = new StringTokenizer(br.readLine());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return st.nextToken();
        }

        int nextInt() {
            return Integer.parseInt(next());
        }

        long nextLong() {
            return Long.parseLong(next());
        }

        double nextDouble() {
            return Double.parseDouble(next());
        }

        String nextLine() {
            String str = "";
            try {
                str = br.readLine();
            } catch (IOException e) {
                e.printStackTrace();
            }
            return str;
        }
    }
    public static FastReader scn = new FastReader();

    // type 0 knight 1 bishop 2 rook
    private static class node implements Comparable<node>{
        int x;
        int y;
        int val;
        int steps;
        int rep;
        int type;

        public node(int x,int y,int val, int st,int rep, int typ){
            this.x = x;
            this.y = y;
            this.rep = rep;
            this.val = val;
            this.steps = st;
            this.type = typ;
        }

        @Override
        public int compareTo(node other){
            if(this.steps == other.steps) return this.rep - other.rep;
            return this.steps - other.steps;
        }
    }

    public static void main(String[] args) {
        int n = scn.nextInt();
        int[][] pos = new int[n*n +1][2] ;
        for(int i = 0; i<n; i++){
            for(int j = 0; j<n; j++){
                pos[scn.nextInt()] = new int[]{i,j};
            }
        }

        PriorityQueue<node> Queue = new PriorityQueue<>();
        Queue.add(new node(pos[1][0],pos[1][1],1,0,0,0));
        Queue.add(new node(pos[1][0],pos[1][1],1,0,0,1));
        Queue.add(new node(pos[1][0],pos[1][1],1,0,0,2));

        int[][][] move = new int[3][][];
        move[0] = new int[][]{{-2,-1},{-1,-2},{2,-1},{1,-2},{-2,1},{-1,2},{2,1},{1,2}};
        move[1] = new int[][]{{-1,-1},{-1,1},{1,-1},{1,1}};
        move[2] = new int[][]{{-1,0},{1,0},{0,-1},{0,1}};

        boolean[][][][] visited = new boolean[n][n][n*n +1][3]; // x,y,value,type

        while(!Queue.isEmpty()){
            node s = Queue.poll();
            if(s.x<0 || s.y<0 || s.x >=n || s.y >=n) continue;
            if(visited[s.x][s.y][s.val][s.type]) continue;
            visited[s.x][s.y][s.val][s.type] = true;
            if(pos[s.val+1][0] == s.x && pos[s.val+1][1] == s.y){
                s.val++;
                if(s.val == n*n){
                    System.out.println(s.steps + " " + s.rep);
                    break;
                }
                visited[s.x][s.y][s.val][s.type] = true;
            }

            for(int i = 0; i< 3; i++) Queue.add(new node(s.x,s.y,s.val,s.steps+1,s.rep+1,i));

            for(int[] m : move[s.type]){
                if(s.type == 0){
                    Queue.add(new node(s.x + m[0], s.y + m[1], s.val , s.steps+1, s.rep , s.type));
                }else{
                    for(int j = 1; j<=n; j++) Queue.add(new node(s.x + j*m[0], s.y + j*m[1], s.val , s.steps+1, s.rep , s.type));
                }
            }
        }
    }
}

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.PriorityQueue;
import java.util.StringTokenizer;

public class D2 {
    static class FastReader {
        BufferedReader br;
        StringTokenizer st;

        public FastReader() {
            br = new BufferedReader(new InputStreamReader(System.in));
        }

        String next() {
            while (st == null || !st.hasMoreElements()) {
                try {
                    st = new StringTokenizer(br.readLine());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return st.nextToken();
        }

        int nextInt() {
            return Integer.parseInt(next());
        }

        long nextLong() {
            return Long.parseLong(next());
        }

        double nextDouble() {
            return Double.parseDouble(next());
        }

        String nextLine() {
            String str = "";
            try {
                str = br.readLine();
            } catch (IOException e) {
                e.printStackTrace();
            }
            return str;
        }
    }
    public static FastReader scn = new FastReader();

    // type 0 knight 1 bishop 2 rook
    private static class node implements Comparable<node>{
        int x;
        int y;
        int val;
        int steps;
        int rep;
        int type;

        public node(int x,int y,int val, int st,int rep, int typ){
            this.x = x;
            this.y = y;
            this.rep = rep;
            this.val = val;
            this.steps = st;
            this.type = typ;
        }

        @Override
        public int compareTo(node other){
            if(this.steps == other.steps) return this.rep - other.rep;
            return this.steps - other.steps;
        }
    }

    public static void main(String[] args) {
        int n = scn.nextInt();
        int[][] pos = new int[n*n +1][2] ;
        for(int i = 0; i<n; i++){
            for(int j = 0; j<n; j++){
                pos[scn.nextInt()] = new int[]{i,j};
            }
        }

        PriorityQueue<node> Queue = new PriorityQueue<>();
        Queue.add(new node(pos[1][0],pos[1][1],1,0,0,0));
        Queue.add(new node(pos[1][0],pos[1][1],1,0,0,1));
        Queue.add(new node(pos[1][0],pos[1][1],1,0,0,2));

        int[][][] move = new int[3][][];
        move[0] = new int[][]{{-2,-1},{-1,-2},{2,-1},{1,-2},{-2,1},{-1,2},{2,1},{1,2}};
        move[1] = new int[][]{{-1,-1},{-1,1},{1,-1},{1,1}};
        move[2] = new int[][]{{-1,0},{1,0},{0,-1},{0,1}};

        boolean[][][][] visited = new boolean[n][n][n*n +1][3]; // x,y,value,type

        while(!Queue.isEmpty()){
            node s = Queue.poll();
            if(s.x<0 || s.y<0 || s.x >=n || s.y >=n) continue;
            if(visited[s.x][s.y][s.val][s.type]) continue;
            visited[s.x][s.y][s.val][s.type] = true;
            if(pos[s.val+1][0] == s.x && pos[s.val+1][1] == s.y){
                s.val++;
                if(s.val == n*n){
                    System.out.println(s.steps + " " + s.rep);
                    break;
                }
                visited[s.x][s.y][s.val][s.type] = true;
            }

            for(int i = 0; i< 3; i++) Queue.add(new node(s.x,s.y,s.val,s.steps+1,s.rep+1,i));

            for(int[] m : move[s.type]){
                if(s.type == 0){
                    for(int j = 0; j<m.length ; j++) Queue.add(new node(s.x + m[0], s.y + m[1], s.val , s.steps+1, s.rep , s.type));
                }else{
                    for(int j = 1; j<=n; j++) Queue.add(new node(s.x + j*m[0], s.y + j*m[1], s.val , s.steps+1, s.rep , s.type));
                }
            }
        }
    }
}

import java.io.OutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.IOException;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.InputStream;

/**
 * Built using CHelper plug-in
 * Actual solution is at the top
 *
 * @author Vadim
 */
public class Main {
    public static void main(String[] args) {
        InputStream inputStream = System.in;
        OutputStream outputStream = System.out;
        InputReader in = new InputReader(inputStream);
        PrintWriter out = new PrintWriter(outputStream);
        TaskD solver = new TaskD();
        solver.solve(1, in, out);
        out.close();
    }

    static class TaskD {
        public static final int MAX_PATH = 1000000;
        public static final int K = 0;
        public static final int B = 1;
        public static final int R = 2;
        Point[] coords;
        int[][] d;
        int[] di = new int[]{2, 2, 1, -1, -2, -2, 1, -1};
        int[] dj = new int[]{-1, 1, 2, 2, 1, -1, -2, -2};
        int n;

        int getBDist(int r, int c, int ra, int ca) {
            if ((r + c) % 2 != (ra + ca) % 2)
                return MAX_PATH;
            if (r - ra == c - ca || r - ra == ca - c)
                return 1;
            return 2;
        }

        int getRDist(int r, int c, int ra, int ca) {
            if (r == ra || c == ca)
                return 1;
            return 2;
        }

        Pair dist(int from, int to, int start, int end) {
            Point p = coords[from];
            Point q = coords[to];
            if (start == end) {
                if (start == K)
                    return new Pair(d[from][to], 0);
                else if (start == B) {
                    return new Pair(getBDist(p.r, p.c, q.r, q.c), 0);//todo:
                } else
                    return new Pair(getRDist(p.r, p.c, q.r, q.c), 0);
            } else {
                if (start == R && end == B || start == B && end == R) {
                    if (getBDist(p.r, p.c, q.r, q.c) == 1)
                        return new Pair(2, 1);
                    if (getRDist(p.r, p.c, q.r, q.c) == 1)
                        return new Pair(2, 1);
                    return new Pair(3, 1);
                }
                if (start == K && end == B || start == B && end == K) {
                    if (end == K) {
                        Point t = p;
                        p = q;
                        q = t;
                    }
                    int min = getBDist(p.r, p.c, q.r, q.c);
                    for (int i = 0; i < 8; i++) {
                        int ni = p.r + di[i];
                        int nj = p.c + dj[i];
                        if (0 <= ni && ni < n && 0 <= nj && nj < n) {
                            min = Math.min(1 + getBDist(ni, nj, q.r, q.c), min);
                        }
                    }
                    return new Pair(min + 1, 1);
                }
                if (start == K && end == R || start == R && end == K) {
                    if (end == K) {
                        Point t = p;
                        p = q;
                        q = t;
                    }
                    int min = getRDist(p.r, p.c, q.r, q.c);
                    for (int i = 0; i < 8; i++) {
                        int ni = p.r + di[i];
                        int nj = p.c + dj[i];
                        if (0 <= ni && ni < n && 0 <= nj && nj < n) {
                            min = Math.min(1 + getRDist(ni, nj, q.r, q.c), min);
                        }
                    }
                    return new Pair(min + 1, 1);
                }
            }
            throw new RuntimeException();

        }

        public void solve(int testNumber, InputReader in, PrintWriter out) {

            n = in.ni();
            int[][] a = new int[n][n];
            for (int i = 0; i < n; i++) {
                for (int j = 0; j < n; j++) {
                    a[i][j] = in.ni() - 1;
                }
            }
            int n2 = n * n;

            d = new int[n2][n2];
            for (int i = 0; i < n2; i++) {
                for (int j = 0; j < n2; j++) {
                    if (i == j)
                        d[i][i] = 0;
                    else
                        d[i][j] = MAX_PATH;
                }
            }

            coords = new Point[n2];
            for (int i = 0; i < n; i++) {
                for (int j = 0; j < n; j++) {
                    coords[a[i][j]] = new Point(i, j);
                    for (int k = 0; k < 8; k++) {
                        int ni = i + di[k];
                        int nj = j + dj[k];
                        if (0 <= ni && ni < n && 0 <= nj && nj < n) {
                            d[a[i][j]][a[ni][nj]] = 1;
                        }
                    }

                }
            }
            for (int k = 0; k < n2; k++) {
                for (int i = 0; i < n2; i++) {
                    for (int j = 0; j < n2; j++) {
                        d[i][j] = Math.min(d[i][j], d[i][k] + d[k][j]);
                    }
                }
            }
            int ans[][] = new int[n2][3];
            int swaps[][] = new int[n2][3];
            for (int i = 0; i < n2 - 1; i++) {
                for (int j = 0; j < 3; j++) {
                    int minDist = MAX_PATH;
                    int minSwap = n2;
                    for (int k = 0; k < 3; k++) {
                        Pair pair = dist(i, i + 1, k, j);
                        int dist = ans[i][k] + pair.x;
                        int swap = swaps[i][k] + pair.y;
                        if (dist < minDist || dist == minDist && swap < minSwap) {
                            minDist = dist;
                            minSwap = swap;
                        }
                    }
                    ans[i + 1][j] += minDist;
                    swaps[i + 1][j] = minSwap;
                }
            }
            int minDist = MAX_PATH;
            int minSwap = n2;
            for (int k = 0; k < 3; k++) {
                int dist = ans[n2 - 1][k];
                int swap = swaps[n2 - 1][k];
                if (dist < minDist || dist == minDist && swap < minSwap) {
                    minDist = dist;
                    minSwap = swap;
                }
            }

            out.println(minDist + " " + minSwap);

        }

        class Point {
            int r;
            int c;

            public Point(int r, int c) {
                this.r = r;
                this.c = c;
            }

        }

        class Pair {
            int x;
            int y;

            public Pair(int x, int y) {
                this.x = x;
                this.y = y;
            }

        }

    }

    static class InputReader extends BufferedReader {
        public InputReader(InputStream st) {
            super(new InputStreamReader(st));
        }

        public String readLine() {
            try {
                return super.readLine();
            } catch (IOException e) {
                return null;
            }
        }

        private int readByte() {
            try {
                return read();
            } catch (IOException e) {
                throw new RuntimeException();
            }
        }

        public int ni() {
            int num = 0, b;
            boolean minus = false;
            while ((b = readByte()) != -1 && !((b >= '0' && b <= '9') || b == '-')) ;
            if (b == '-') {
                minus = true;
                b = readByte();
            }

            while (true) {
                if (b >= '0' && b <= '9') {
                    num = num * 10 + (b - '0');
                } else {
                    return minus ? -num : num;
                }
                b = readByte();
            }
        }

    }
}

import java.io.OutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.PrintStream;
import java.io.IOException;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.InputStream;

/**
 * Built using CHelper plug-in
 * Actual solution is at the top
 *
 * @author Vadim
 */
public class Main {
    public static void main(String[] args) {
        InputStream inputStream = System.in;
        OutputStream outputStream = System.out;
        InputReader in = new InputReader(inputStream);
        PrintWriter out = new PrintWriter(outputStream);
        TaskD solver = new TaskD();
        solver.solve(1, in, out);
        out.close();
    }

    static class TaskD {
        public static final int MAX_PATH = 1000000;
        public static final int K = 0;
        public static final int B = 1;
        public static final int R = 2;

        static int getBDist(int r, int c, int ra, int ca) {
            if ((r + c) % 2 != (ra + ca) % 2)
                return MAX_PATH;
            if (r - ra == c - ca || r - ra == ca - c)
                return 1;
            return 2;
        }

        static int getRDist(int r, int c, int ra, int ca) {
            if (r == ra || c == ca)
                return 1;
            return 2;
        }

        public void solve(int testNumber, InputReader in, PrintWriter out) {
            int n = in.ni();
            int[][] a = new int[n][n];
            for (int i = 0; i < n; i++) {
                for (int j = 0; j < n; j++) {
                    a[i][j] = in.ni() - 1;
                }
            }
            int n2 = n * n;
            int[][] d = new int[n2][n2];
            for (int i = 0; i < n2; i++) {
                for (int j = 0; j < n2; j++) {
                    if (i == j)
                        d[i][i] = 0;
                    else
                        d[i][j] = MAX_PATH;
                }
            }
            int[] di = new int[]{2, 2, 1, -1, -2, -2, 1, -1};
            int[] dj = new int[]{-1, 1, 2, 2, 1, -1, -2, -2};
            Point[] coords = new Point[n2];
            for (int i = 0; i < n; i++) {
                for (int j = 0; j < n; j++) {
                    coords[a[i][j]] = new Point(i, j);
                    for (int k = 0; k < 8; k++) {
                        int ni = i + di[k];
                        int nj = j + dj[k];
                        if (0 <= ni && ni < n && 0 <= nj && nj < n) {
                            d[a[i][j]][a[ni][nj]] = 1;
                        }
                    }

                }
            }
            for (int k = 0; k < n2; k++) {
                for (int i = 0; i < n2; i++) {
                    for (int j = 0; j < n2; j++) {
                        d[i][j] = Math.min(d[i][j], d[i][k] + d[k][j]);
                    }
                }
            }
            int ans[][] = new int[n2][3];
            int swaps[][] = new int[n2][3];
            for (int i = 0; i < n2 - 1; i++) {
                Point p = coords[i];
                Point next = coords[i + 1];
                ans[i + 1][K] = d[i][i + 1];
                ans[i + 1][B] = getBDist(p.r, p.c, next.r, next.c);
                ans[i + 1][R] = getRDist(p.r, p.c, next.r, next.c);
                for (int j = 0; j < 3; j++) {
                    int minDist = MAX_PATH;
                    int minSwap = n2;
                    for (int k = 0; k < 3; k++) {
                        int dist = ans[i][k];
                        int swap = swaps[i][k];
                        if (j != k) {
                            dist++;
                            swap++;
                        }
                        if (dist < minDist || dist == minDist && swap < minSwap) {
                            minDist = dist;
                            minSwap = swap;
                        }
                    }
                    ans[i + 1][j] += minDist;
                    swaps[i + 1][j] = minSwap;
                }
            }
            int minDist = MAX_PATH;
            int minSwap = n2;
            for (int k = 0; k < 3; k++) {
                int dist = ans[n2 - 1][k];
                int swap = swaps[n2 - 1][k];
                if (dist < minDist || dist == minDist && swap < minSwap) {
                    minDist = dist;
                    minSwap = swap;
                }
            }

            System.out.println(minDist + " " + minSwap);

        }

        class Point {
            int r;
            int c;

            public Point(int r, int c) {
                this.r = r;
                this.c = c;
            }

        }

    }

    static class InputReader extends BufferedReader {
        public InputReader(InputStream st) {
            super(new InputStreamReader(st));
        }

        public String readLine() {
            try {
                return super.readLine();
            } catch (IOException e) {
                return null;
            }
        }

        private int readByte() {
            try {
                return read();
            } catch (IOException e) {
                throw new RuntimeException();
            }
        }

        public int ni() {
            int num = 0, b;
            boolean minus = false;
            while ((b = readByte()) != -1 && !((b >= '0' && b <= '9') || b == '-')) ;
            if (b == '-') {
                minus = true;
                b = readByte();
            }

            while (true) {
                if (b >= '0' && b <= '9') {
                    num = num * 10 + (b - '0');
                } else {
                    return minus ? -num : num;
                }
                b = readByte();
            }
        }

    }
}

import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.*;

public class d{

	static class Node{
		int i, j, from, to, cost;
		Node(int i, int j, int from, int to, int cost){this.i=i;this.j=j;this.from=from;this.to=to;this.cost=cost;}
	}

	static class Node2{
		int i, j, type, cost, n;
		Node2(int i, int j, int t, int c, int n){this.i=i;this.j=j;type=t;cost=c;this.n=n;}
	}

	static class Pos{
		int i, j;
		Pos(int i, int j){this.i=i;this.j=j;}
	}

	static void solve(){
		int n = ni();
		int[][][][] dis = new int[n][n][3][3];
		for(int i=0;i<n;++i)for(int j=0;j<n;++j)for(int k=0;k<3;++k)for(int l=0;l<3;++l)dis[i][j][k][l]=1000000;
		PriorityQueue<Node> que = new PriorityQueue<>((a,b)->a.cost-b.cost);
		for(int i=0;i<3;++i){
			dis[0][0][i][i]=0;
			que.add(new Node(0,0,i,i, 0));
		}
		int[] kx = {-2, -2, -1, -1, 1, 1, 2, 2};
		int[] ky = {1, -1, 2, -2, 2, -2, 1, -1};

		while(!que.isEmpty()){
			Node node = que.poll();
			if(dis[node.i][node.j][node.from][node.to]<node.cost)continue;
			for(int i=0;i<3;++i){
				if(i==node.to)continue;
				if(dis[node.i][node.j][node.from][i] > node.cost+1001){
					dis[node.i][node.j][node.from][i]=node.cost+1001;
					que.add(new Node(node.i, node.j, node.from, i, node.cost+1001));
				}
			}
			if(node.to==0){
				for(int k=0;k<kx.length;++k){
					int ni = node.i+kx[k], nj = node.j+ky[k];
					if(inrange(node.i+kx[k], node.j+ky[k], n, n) && dis[node.i+kx[k]][node.j+ky[k]][node.from][node.to] > node.cost+1000){
						dis[ni][nj][node.from][node.to]=node.cost+1000;
						que.add(new Node(ni, nj, node.from, node.to, node.cost+1000));
					}
				}
			}else if(node.to==1){
				for(int k=0;k<n;++k){
					int ni = k, nj=node.j;
					if(dis[ni][nj][node.from][node.to] > node.cost+1000){
						dis[ni][nj][node.from][node.to]=node.cost+1000;
						que.add(new Node(ni, nj, node.from, node.to, node.cost+1000));
					}
				}
				for(int k=0;k<n;++k){
					int ni = node.i, nj=k;
					if(dis[ni][nj][node.from][node.to] > node.cost+1000){
						dis[ni][nj][node.from][node.to]=node.cost+1000;
						que.add(new Node(ni, nj, node.from, node.to, node.cost+1000));
					}
				}
			}else{
				for(int k=-n;k<=n;++k){
					int ni = node.i+k, nj=node.j+k;
					if(inrange(ni, nj, n, n) && dis[ni][nj][node.from][node.to] > node.cost+1000){
						dis[ni][nj][node.from][node.to]=node.cost+1000;
						que.add(new Node(ni, nj, node.from, node.to, node.cost+1000));
					}
					nj = node.j-k;
					if(inrange(ni, nj, n, n) && dis[ni][nj][node.from][node.to] > node.cost+1000){
						dis[ni][nj][node.from][node.to]=node.cost+1000;
						que.add(new Node(ni, nj, node.from, node.to, node.cost+1000));
					}
				}
			}
		}
		PriorityQueue<Node2> que2 = new PriorityQueue<>((a,b)->a.cost-b.cost);
		int[][][] dis2 = new int[n][n][3];
		for(int i=0;i<n;++i)for(int j=0;j<n;++j)for(int k=0;k<3;++k)dis2[i][j][k]=1000000000;

		Pos[] pos = new Pos[n*n];
		for(int i=0;i<n;++i)for(int j=0;j<n;++j){
			pos[ni()-1] = new Pos(i, j);
		}

		for(int i=0;i<3;++i){
			dis2[pos[0].i][pos[0].j][i]=0;
			que2.add(new Node2(pos[0].i, pos[0].j, i, 0, 0 ));
		}
		while(!que2.isEmpty()){
			Node2 node = que2.poll();
			if(dis2[node.i][node.j][node.type]<node.cost)continue;
			if(node.n == n*n-1){
				out.println(node.cost/1000+" "+node.cost%1000);
				return;
			}
			int ni = pos[node.n+1].i, nj = pos[node.n+1].j;
			for(int t=0;t<3;++t){
				int d = node.cost/1000;
				int c = node.cost%1000;
				d += dis[Math.abs(ni-node.i)][Math.abs(nj-node.j)][node.type][t]/1000;
				c += dis[Math.abs(ni-node.i)][Math.abs(nj-node.j)][node.type][t]%1000;
				int nc = d*1000+c;
				if(dis2[ni][nj][t] > nc){
					dis2[ni][nj][t] = nc;
					que2.add(new Node2(ni, nj, t, dis2[ni][nj][t], node.n+1));
				}
			}
		}

	}

	 public static void main(String[] args){
		 solve();
		 out.flush();
	 }
	 private static InputStream in = System.in;
	 private static PrintWriter out = new PrintWriter(System.out);

	 static boolean inrange(int y, int x, int h, int w){
		 return y>=0 && y<h && x>=0 && x<w;
	 }
	 @SuppressWarnings("unchecked")
	 static<T extends Comparable> int lower_bound(List<T> list, T key){
		 int lower=-1;int upper=list.size();
		 while(upper - lower>1){
		 int center =(upper+lower)/2;
		 if(list.get(center).compareTo(key)>=0)upper=center;
		 else lower=center;
		 }
		 return upper;
	 }
	 @SuppressWarnings("unchecked")
	 static <T extends Comparable> int upper_bound(List<T> list, T key){
		 int lower=-1;int upper=list.size();
		 while(upper-lower >1){
		 int center=(upper+lower)/2;
		 if(list.get(center).compareTo(key)>0)upper=center;
		 else lower=center;
		 }
		 return upper;
	 }
	 @SuppressWarnings("unchecked")
	 static <T extends Comparable> boolean next_permutation(List<T> list){
		 int lastIndex = list.size()-2;
		 while(lastIndex>=0 && list.get(lastIndex).compareTo(list.get(lastIndex+1))>=0)--lastIndex;
		 if(lastIndex<0)return false;
		 int swapIndex = list.size()-1;
		 while(list.get(lastIndex).compareTo(list.get(swapIndex))>=0)swapIndex--;
		 T tmp = list.get(lastIndex);
		 list.set(lastIndex++, list.get(swapIndex));
		 list.set(swapIndex, tmp);
		 swapIndex = list.size()-1;
		 while(lastIndex<swapIndex){
		 tmp = list.get(lastIndex);
		 list.set(lastIndex, list.get(swapIndex));
		 list.set(swapIndex, tmp);
		 ++lastIndex;--swapIndex;
		 }
		 return true;
	 }
	 private static final byte[] buffer = new byte[1<<15];
	 private static int ptr = 0;
	 private static int buflen = 0;
	 private static boolean hasNextByte(){
		 if(ptr<buflen)return true;
		 ptr = 0;
		 try{
			 buflen = in.read(buffer);
		 } catch (IOException e){
			 e.printStackTrace();
		 }
		 return buflen>0;
	 }
	 private static int readByte(){ if(hasNextByte()) return buffer[ptr++]; else return -1;}
	 private static boolean isSpaceChar(int c){ return !(33<=c && c<=126);}
	 private static int skip(){int res; while((res=readByte())!=-1 && isSpaceChar(res)); return res;}

	 private static double nd(){ return Double.parseDouble(ns()); }
	 private static char nc(){ return (char)skip(); }
	 private static String ns(){
		 StringBuilder sb = new StringBuilder();
		 for(int b=skip();!isSpaceChar(b);b=readByte())sb.append((char)b);
		 return sb.toString();
	 }
	 private static int[] nia(int n){
		 int[] res = new int[n];
		 for(int i=0;i<n;++i)res[i]=ni();
		 return res;
	 }
	 private static long[] nla(int n){
		 long[] res = new long[n];
		 for(int i=0;i<n;++i)res[i]=nl();
		 return res;
	 }
	 private static int ni(){
		 int res=0,b;
		 boolean minus=false;
		 while((b=readByte())!=-1 && !((b>='0'&&b<='9') || b=='-'));
		 if(b=='-'){
			 minus=true;
			 b=readByte();
		 }
		 for(;'0'<=b&&b<='9';b=readByte())res=res*10+(b-'0');
		 return minus ? -res:res;
	 }
	 private static long nl(){
		 long res=0,b;
		 boolean minus=false;
		 while((b=readByte())!=-1 && !((b>='0'&&b<='9') || b=='-'));
		 if(b=='-'){
			 minus=true;
			 b=readByte();
		 }
		 for(;'0'<=b&&b<='9';b=readByte())res=res*10+(b-'0');
		 return minus ? -res:res;
	}
}

import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.*;

public class d{

	static class Node{
		int i, j, from, to, cost;
		Node(int i, int j, int from, int to, int cost){this.i=i;this.j=j;this.from=from;this.to=to;this.cost=cost;}
	}

	static class Node2{
		int i, j, type, cost, n;
		Node2(int i, int j, int t, int c, int n){this.i=i;this.j=j;type=t;cost=c;this.n=n;}
	}

	static class Pos{
		int i, j;
		Pos(int i, int j){this.i=i;this.j=j;}
	}

	static void solve(){
		int n = ni();
		int[][][][] dis = new int[n][n][3][3];
		for(int i=0;i<n;++i)for(int j=0;j<n;++j)for(int k=0;k<3;++k)for(int l=0;l<3;++l)dis[i][j][k][l]=1000000;
		PriorityQueue<Node> que = new PriorityQueue<>((a,b)->a.cost-b.cost);
		for(int i=0;i<3;++i){
			dis[0][0][i][i]=0;
			que.add(new Node(0,0,i,i, 0));
		}
		int[] kx = {-2, -2, -1, -1, 1, 1, 2, 2};
		int[] ky = {1, -1, 2, -2, 2, -2, 1, -1};

		while(!que.isEmpty()){
			Node node = que.poll();
			if(dis[node.i][node.j][node.from][node.to]<node.cost)continue;
			for(int i=0;i<3;++i){
				if(i==node.to)continue;
				if(dis[node.i][node.j][node.from][i] > node.cost+1){
					dis[node.i][node.j][node.from][i]=node.cost+1;
					que.add(new Node(node.i, node.j, node.from, i, node.cost+1));
				}
			}
			if(node.to==0){
				for(int k=0;k<kx.length;++k){
					int ni = node.i+kx[k], nj = node.j+ky[k];
					if(inrange(node.i+kx[k], node.j+ky[k], n, n) && dis[node.i+kx[k]][node.j+ky[k]][node.from][node.to] > node.cost+1){
						dis[ni][nj][node.from][node.to]=node.cost+1;
						que.add(new Node(ni, nj, node.from, node.to, node.cost+1));
					}
				}
			}else if(node.to==1){
				for(int k=0;k<n;++k){
					int ni = k, nj=node.j;
					if(dis[ni][nj][node.from][node.to] > node.cost+1){
						dis[ni][nj][node.from][node.to]=node.cost+1;
						que.add(new Node(ni, nj, node.from, node.to, node.cost+1));
					}
				}
				for(int k=0;k<n;++k){
					int ni = node.i, nj=k;
					if(dis[ni][nj][node.from][node.to] > node.cost+1){
						dis[ni][nj][node.from][node.to]=node.cost+1;
						que.add(new Node(ni, nj, node.from, node.to, node.cost+1));
					}
				}
			}else{
				for(int k=-n;k<=n;++k){
					int ni = node.i+k, nj=node.j+k;
					if(inrange(ni, nj, n, n) && dis[ni][nj][node.from][node.to] > node.cost+1){
						dis[ni][nj][node.from][node.to]=node.cost+1;
						que.add(new Node(ni, nj, node.from, node.to, node.cost+1));
					}
					nj = node.j-k;
					if(inrange(ni, nj, n, n) && dis[ni][nj][node.from][node.to] > node.cost+1){
						dis[ni][nj][node.from][node.to]=node.cost+1;
						que.add(new Node(ni, nj, node.from, node.to, node.cost+1));
					}
				}
			}
		}
		PriorityQueue<Node2> que2 = new PriorityQueue<>((a,b)->a.cost-b.cost);
		int[][][] dis2 = new int[n][n][3];
		for(int i=0;i<n;++i)for(int j=0;j<n;++j)for(int k=0;k<3;++k)dis2[i][j][k]=100000000;

		Pos[] pos = new Pos[n*n];
		for(int i=0;i<n;++i)for(int j=0;j<n;++j){
			pos[ni()-1] = new Pos(i, j);
		}

		for(int i=0;i<3;++i){
			dis2[pos[0].i][pos[0].j][i]=0;
			que2.add(new Node2(pos[0].i, pos[0].j, i, 0, 0 ));
		}
		while(!que2.isEmpty()){
			Node2 node = que2.poll();
			if(dis2[node.i][node.j][node.type]<node.cost)continue;
			if(node.n == n*n-1){
				out.println(node.cost/1000+" "+node.cost%1000);
				return;
			}
			int ni = pos[node.n+1].i, nj = pos[node.n+1].j;
			for(int t=0;t<3;++t){
				int d = node.cost/1000;
				int c = node.cost%1000;
				d += dis[Math.abs(ni-node.i)][Math.abs(nj-node.j)][node.type][t];
				if(t != node.type)c++;
				int nc = d*1000+c;
				if(dis2[ni][nj][t] > nc){
					dis2[ni][nj][t] = nc;
					que2.add(new Node2(ni, nj, t, dis2[ni][nj][t], node.n+1));
				}
			}
		}

	}

	 public static void main(String[] args){
		 solve();
		 out.flush();
	 }
	 private static InputStream in = System.in;
	 private static PrintWriter out = new PrintWriter(System.out);

	 static boolean inrange(int y, int x, int h, int w){
		 return y>=0 && y<h && x>=0 && x<w;
	 }
	 @SuppressWarnings("unchecked")
	 static<T extends Comparable> int lower_bound(List<T> list, T key){
		 int lower=-1;int upper=list.size();
		 while(upper - lower>1){
		 int center =(upper+lower)/2;
		 if(list.get(center).compareTo(key)>=0)upper=center;
		 else lower=center;
		 }
		 return upper;
	 }
	 @SuppressWarnings("unchecked")
	 static <T extends Comparable> int upper_bound(List<T> list, T key){
		 int lower=-1;int upper=list.size();
		 while(upper-lower >1){
		 int center=(upper+lower)/2;
		 if(list.get(center).compareTo(key)>0)upper=center;
		 else lower=center;
		 }
		 return upper;
	 }
	 @SuppressWarnings("unchecked")
	 static <T extends Comparable> boolean next_permutation(List<T> list){
		 int lastIndex = list.size()-2;
		 while(lastIndex>=0 && list.get(lastIndex).compareTo(list.get(lastIndex+1))>=0)--lastIndex;
		 if(lastIndex<0)return false;
		 int swapIndex = list.size()-1;
		 while(list.get(lastIndex).compareTo(list.get(swapIndex))>=0)swapIndex--;
		 T tmp = list.get(lastIndex);
		 list.set(lastIndex++, list.get(swapIndex));
		 list.set(swapIndex, tmp);
		 swapIndex = list.size()-1;
		 while(lastIndex<swapIndex){
		 tmp = list.get(lastIndex);
		 list.set(lastIndex, list.get(swapIndex));
		 list.set(swapIndex, tmp);
		 ++lastIndex;--swapIndex;
		 }
		 return true;
	 }
	 private static final byte[] buffer = new byte[1<<15];
	 private static int ptr = 0;
	 private static int buflen = 0;
	 private static boolean hasNextByte(){
		 if(ptr<buflen)return true;
		 ptr = 0;
		 try{
			 buflen = in.read(buffer);
		 } catch (IOException e){
			 e.printStackTrace();
		 }
		 return buflen>0;
	 }
	 private static int readByte(){ if(hasNextByte()) return buffer[ptr++]; else return -1;}
	 private static boolean isSpaceChar(int c){ return !(33<=c && c<=126);}
	 private static int skip(){int res; while((res=readByte())!=-1 && isSpaceChar(res)); return res;}

	 private static double nd(){ return Double.parseDouble(ns()); }
	 private static char nc(){ return (char)skip(); }
	 private static String ns(){
		 StringBuilder sb = new StringBuilder();
		 for(int b=skip();!isSpaceChar(b);b=readByte())sb.append((char)b);
		 return sb.toString();
	 }
	 private static int[] nia(int n){
		 int[] res = new int[n];
		 for(int i=0;i<n;++i)res[i]=ni();
		 return res;
	 }
	 private static long[] nla(int n){
		 long[] res = new long[n];
		 for(int i=0;i<n;++i)res[i]=nl();
		 return res;
	 }
	 private static int ni(){
		 int res=0,b;
		 boolean minus=false;
		 while((b=readByte())!=-1 && !((b>='0'&&b<='9') || b=='-'));
		 if(b=='-'){
			 minus=true;
			 b=readByte();
		 }
		 for(;'0'<=b&&b<='9';b=readByte())res=res*10+(b-'0');
		 return minus ? -res:res;
	 }
	 private static long nl(){
		 long res=0,b;
		 boolean minus=false;
		 while((b=readByte())!=-1 && !((b>='0'&&b<='9') || b=='-'));
		 if(b=='-'){
			 minus=true;
			 b=readByte();
		 }
		 for(;'0'<=b&&b<='9';b=readByte())res=res*10+(b-'0');
		 return minus ? -res:res;
	}
}

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StreamTokenizer;

public class Main {

    static int n;
    static int[] x;
    static int[] y;

    // 0代表马, 1代表象, 2代表车
    public static void main(String[] args) throws IOException {

        StreamTokenizer streamTokenizer = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
        streamTokenizer.nextToken();
        n = (int) streamTokenizer.nval;
        x = new int[n * n + 1];
        y = new int[n * n + 1];
        int[][] f = new int[3][n * n + 1];
        int[][] g = new int[3][n * n + 1];
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= n; j++) {
                streamTokenizer.nextToken();
                x[(int) streamTokenizer.nval] = i;
                y[(int) streamTokenizer.nval] = j;
            }
        f[0][1] = 0;
        f[1][1] = 0;
        f[2][1] = 0;
        g[0][1] = 0;
        g[1][1] = 0;
        g[2][1] = 0;
        for (int i = 2; i <= n * n; i++) {
            for (int j = 0; j < 3; j++) {
                getMinStep(x[i - 1], y[i - 1], 0, x[i], y[i], j);
                f[j][i] = f[0][i - 1] + minStep;
                g[j][i] = g[0][i - 1] + minMove;
                getMinStep(x[i - 1], y[i - 1], 1, x[i], y[i], j);
                if ((f[1][i - 1] + minStep < f[j][i]) || ((f[1][i - 1] + minStep == f[j][i]) && (g[1][i - 1] + minMove < g[j][i]))) {
                    f[j][i] = f[1][i - 1] + minStep;
                    g[j][i] = g[1][i - 1] + minMove;
                }
                getMinStep(x[i - 1], y[i - 1], 2, x[i], y[i], j);
                if ((f[2][i - 1] + minStep < f[j][i]) || ((f[2][i - 1] + minStep == f[j][i]) && (g[2][i - 1] + minMove < g[j][i]))) {
                    f[j][i] = f[2][i - 1] + minStep;
                    g[j][i] = g[2][i - 1] + minMove;
                }
            }
        }
        minStep = f[0][n * n];
        minMove = g[0][n * n];

        if ((f[1][n * n] < minStep) || ((f[1][n * n] == minStep) && (g[1][n * n] < minMove))) {
            minStep = f[1][n * n];
            minMove = g[1][n * n];
        }
        if ((f[2][n * n] < minStep) || ((f[2][n * n] == minStep) && (g[2][n * n] < minMove))) {
            minStep = f[2][n * n];
            minMove = g[2][n * n];
        }
        System.out.print(minStep + " " + minMove);
    }

    static int[] pointX = new int[1000000];
    static int[] pointY = new int[1000000];
    static int[] state = new int[1000000];
    static int[] steps = new int[1000000];
    static int[] moves = new int[1000000];

    static int[][] knight = {
            {-2, -1},
            {-2, 1},
            {-1, -2},
            {-1, 2},
            {1, -2},
            {1, 2},
            {2, -1},
            {2, 1}
    };

    static int minStep;
    static int minMove;

    // 0代表马, 1代表象, 2代表车
    public static void getMinStep(int fromX, int fromY, int fromState, int toX, int toY, int toState) {

        int l = 0, r = 0;
        int tempX, tempY;
        pointX[0] = fromX;
        pointY[0] = fromY;
        state[0] = fromState;
        steps[0] = 0;
        moves[0] = 0;
        while (l <= r) {
            if (state[l] == 0) {
                for (int i = 0; i < knight.length; i++) {
                    tempX = pointX[l] + knight[i][0];
                    tempY = pointY[l] + knight[i][1];
                    if (isInMatrix(tempX, tempY)) {
                        r++;
                        pointX[r] = tempX;
                        pointY[r] = tempY;
                        state[r] = 0;
                        steps[r] = steps[l] + 1;
                        moves[r] = moves[l];
                        if (tempX == toX && tempY == toY && 0 == toState) {
                            minStep = steps[r];
                            minMove = moves[r];
                            return;
                        }
                    }
                }

                r++;
                pointX[r] = pointX[l];
                pointY[r] = pointY[l];
                state[r] = 1;
                steps[r] = steps[l] + 1;
                moves[r] = moves[l] + 1;
                if (pointX[r] == toX && pointY[r] == toY && 1 == toState) {
                    minStep = steps[r];
                    minMove = moves[r];
                    return;
                }

                r++;
                pointX[r] = pointX[l];
                pointY[r] = pointY[l];
                state[r] = 2;
                steps[r] = steps[l] + 1;
                moves[r] = moves[l] + 1;
                if (pointX[r] == toX && pointY[r] == toY && 2 == toState) {
                    minStep = steps[r];
                    minMove = moves[r];
                    return;
                }
            } else if (state[l] == 1) {
                tempX = pointX[l];
                tempY = pointY[l];
                for (; true;) {
                    tempX += 1;
                    tempY += 1;
                    if (isInMatrix(tempX, tempY)) {
                        r++;
                        pointX[r] = tempX;
                        pointY[r] = tempY;
                        state[r] = 1;
                        steps[r] = steps[l] + 1;
                        moves[r] = moves[l];
                        if (tempX == toX && tempY == toY && 1 == toState) {
                            minStep = steps[r];
                            minMove = moves[r];
                            return;
                        }
                    } else
                        break;
                }

                tempX = pointX[l];
                tempY = pointY[l];
                for (; true;) {
                    tempX += 1;
                    tempY += -1;
                    if (isInMatrix(tempX, tempY)) {
                        r++;
                        pointX[r] = tempX;
                        pointY[r] = tempY;
                        state[r] = 1;
                        steps[r] = steps[l] + 1;
                        moves[r] = moves[l];
                        if (tempX == toX && tempY == toY && 1 == toState) {
                            minStep = steps[r];
                            minMove = moves[r];
                            return;
                        }
                    } else
                        break;
                }

                tempX = pointX[l];
                tempY = pointY[l];
                for (; true;) {
                    tempX += -1;
                    tempY += 1;
                    if (isInMatrix(tempX, tempY)) {
                        r++;
                        pointX[r] = tempX;
                        pointY[r] = tempY;
                        state[r] = 1;
                        steps[r] = steps[l] + 1;
                        moves[r] = moves[l];
                        if (tempX == toX && tempY == toY && 1 == toState) {
                            minStep = steps[r];
                            minMove = moves[r];
                            return;
                        }
                    } else
                        break;
                }

                tempX = pointX[l];
                tempY = pointY[l];
                for (; true;) {
                    tempX += -1;
                    tempY += -1;
                    if (isInMatrix(tempX, tempY)) {
                        r++;
                        pointX[r] = tempX;
                        pointY[r] = tempY;
                        state[r] = 1;
                        steps[r] = steps[l] + 1;
                        moves[r] = moves[l];
                        if (tempX == toX && tempY == toY && 1 == toState) {
                            minStep = steps[r];
                            minMove = moves[r];
                            return;
                        }
                    } else
                        break;
                }

                r++;
                pointX[r] = pointX[l];
                pointY[r] = pointY[l];
                state[r] = 0;
                steps[r] = steps[l] + 1;
                moves[r] = moves[l] + 1;
                if (pointX[r] == toX && pointY[r] == toY && 0 == toState) {
                    minStep = steps[r];
                    minMove = moves[r];
                    return;
                }

                r++;
                pointX[r] = pointX[l];
                pointY[r] = pointY[l];
                state[r] = 2;
                steps[r] = steps[l] + 1;
                moves[r] = moves[l] + 1;
                if (pointX[r] == toX && pointY[r] == toY && 2 == toState) {
                    minStep = steps[r];
                    minMove = moves[r];
                    return;
                }

            } else {
                tempX = pointX[l];
                tempY = pointY[l];
                for (; true;) {
                    tempX += 1;
                    if (isInMatrix(tempX, tempY)) {
                        r++;
                        pointX[r] = tempX;
                        pointY[r] = tempY;
                        state[r] = 2;
                        steps[r] = steps[l] + 1;
                        moves[r] = moves[l];
                        if (tempX == toX && tempY == toY) {
                            minStep = steps[r];
                            minMove = moves[r];
                            return;
                        }
                    } else
                        break;
                }

                tempX = pointX[l];
                tempY = pointY[l];
                for (; true;) {
                    tempX += -1;
                    if (isInMatrix(tempX, tempY)) {
                        r++;
                        pointX[r] = tempX;
                        pointY[r] = tempY;
                        state[r] = 2;
                        steps[r] = steps[l] + 1;
                        moves[r] = moves[l];
                        if (tempX == toX && tempY == toY && 2 == toState) {
                            minStep = steps[r];
                            minMove = moves[r];
                            return;
                        }
                    } else
                        break;
                }

                tempX = pointX[l];
                tempY = pointY[l];
                for (; true;) {
                    tempY += 1;
                    if (isInMatrix(tempX, tempY)) {
                        r++;
                        pointX[r] = tempX;
                        pointY[r] = tempY;
                        state[r] = 2;
                        steps[r] = steps[l] + 1;
                        moves[r] = moves[l];
                        if (tempX == toX && tempY == toY && 2 == toState) {
                            minStep = steps[r];
                            minMove = moves[r];
                            return;
                        }
                    } else
                        break;
                }

                tempX = pointX[l];
                tempY = pointY[l];
                for (; true;) {
                    tempY += -1;
                    if (isInMatrix(tempX, tempY)) {
                        r++;
                        pointX[r] = tempX;
                        pointY[r] = tempY;
                        state[r] = 2;
                        steps[r] = steps[l] + 1;
                        moves[r] = moves[l];
                        if (tempX == toX && tempY == toY && 2 == toState) {
                            minStep = steps[r];
                            minMove = moves[r];
                            return;
                        }
                    } else
                        break;
                }

                r++;
                pointX[r] = pointX[l];
                pointY[r] = pointY[l];
                state[r] = 0;
                steps[r] = steps[l] + 1;
                moves[r] = moves[l] + 1;
                if (pointX[r] == toX && pointY[r] == toY && 0 == toState) {
                    minStep = steps[r];
                    minMove = moves[r];
                    return;
                }

                r++;
                pointX[r] = pointX[l];
                pointY[r] = pointY[l];
                state[r] = 1;
                steps[r] = steps[l] + 1;
                moves[r] = moves[l] + 1;
                if (pointX[r] == toX && pointY[r] == toY && 1 == toState) {
                    minStep = steps[r];
                    minMove = moves[r];
                    return;
                }
            }
            l++;
        }
    }

    public static boolean isInMatrix(int x, int y) {
        if (x >= 1 && x <= n && y >= 1 && y <= n)
            return true;
        return false;
    }

}

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.LinkedList;
import java.util.PriorityQueue;
import java.util.StringTokenizer;

public class D {
    static class FastReader {
        BufferedReader br;
        StringTokenizer st;

        public FastReader() {
            br = new BufferedReader(new InputStreamReader(System.in));
        }

        String next() {
            while (st == null || !st.hasMoreElements()) {
                try {
                    st = new StringTokenizer(br.readLine());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return st.nextToken();
        }

        int nextInt() {
            return Integer.parseInt(next());
        }

        long nextLong() {
            return Long.parseLong(next());
        }

        double nextDouble() {
            return Double.parseDouble(next());
        }

        String nextLine() {
            String str = "";
            try {
                str = br.readLine();
            } catch (IOException e) {
                e.printStackTrace();
            }
            return str;
        }
    }
    public static FastReader scn = new FastReader();

    public static void main(String[] args) {
        int n = scn.nextInt();
        int[] ii = new int[n*n+1] ,jj = new int[n*n+1];
        for(int i = 1; i<=n; i++){
            for(int j = 1; j<= n; j++){
                int a = scn.nextInt();
                ii[a] = i;
                jj[a] = j;
            }
        }
        int[][] mat = new int[n*n][3];
        for(int i = 1; i< n*n; i++){
            mat[i][0] = knight(n,ii[i],jj[i],ii[i+1],jj[i+1]);
            mat[i][1] = bishop(ii[i],jj[i],ii[i+1],jj[i+1]);
            mat[i][2] = rook(ii[i],jj[i],ii[i+1],jj[i+1]);
        }

        heapmover res = helper(mat,0,0);
        System.out.println(res.value + " " + res.rep);
    }

    private static class heapmover implements Comparable<heapmover>{
        int value;
        int rep;

        @Override
        public int compareTo(heapmover o) {
            if(this.value==o.value) return this.rep - o.rep;
            return this.value - o.value;
        }
    }

    private static heapmover helper(int[][] mat, int i, int j){
        if(i==mat.length) return new heapmover();

        if(mat[i][j]==-1){
            heapmover hm = new heapmover();
            hm.value = 10000000;
            return hm;
        }
        heapmover a = helper(mat,i+1,j);
        heapmover b = helper(mat,i+1,(j+1)%3);
        heapmover c = helper(mat,i+1,(j+2)%3);

        a.value = a.value + mat[i][j];
        b.value = b.value + mat[i][j] + 1;
        b.rep = b.rep + 1;
        c.value = c.value + mat[i][j] + 1;
        c.rep = c.rep + 1;

        PriorityQueue<heapmover> heap = new PriorityQueue<>();
        heap.add(a);
        heap.add(b);
        heap.add(c);

        return heap.poll();
    }

    private static int rook(int is, int js, int id, int jd){
        if(is==id || jd==js) return 1;
        return 2;
    }

    private static int bishop(int is, int js, int id, int jd){
        if(Math.abs(is-id) == Math.abs(js-jd)) return 1;
        if((Math.abs(is-id)+Math.abs(js-jd))%2==0) return 2;
        return -1;
    }

    private static class cell{
        int x, y , distance;
    }

    private static int knight(int n , int is, int js, int id, int jd){
        return bfs(n,n,is,js,id,jd);
    }

    private static int bfs(int n ,int m , int r, int c, int d1, int d2){
        int[] a = {-2,-2,-1,-1,1,1,2,2};
        int[] b = {-1,1,-2,2,-2,2,-1,1};

        boolean[][] visited = new boolean[n+1][m+1];

        LinkedList<cell> queue = new LinkedList<>();
        cell start = new cell();
        start.distance = 0;
        start.x = r;
        start.y = c;
        queue.addLast(start);

        while (!queue.isEmpty()){
            cell t = queue.removeFirst();
            visited[t.x][t.y] = true;

            if(t.x==d1 && t.y==d2) return t.distance;

            for(int i = 0 ; i< 8 ; i++){
                int x = t.x + a[i];
                int y = t.y + b[i];

                if(check(n,m,x,y) && !visited[x][y]){
                    cell aa = new cell();
                    aa.x = x;
                    aa.y = y;
                    aa.distance = t.distance+1;
                    queue.addLast(aa);
                }
            }

        }

        return -1;
    }

    private static boolean check(int n , int m , int r , int c){
        if(r>n || r <=0 || c>m || c<=0) return false;
        return true;
    }
}

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.LinkedList;
import java.util.PriorityQueue;
import java.util.StringTokenizer;

public class D {
    static class FastReader {
        BufferedReader br;
        StringTokenizer st;

        public FastReader() {
            br = new BufferedReader(new InputStreamReader(System.in));
        }

        String next() {
            while (st == null || !st.hasMoreElements()) {
                try {
                    st = new StringTokenizer(br.readLine());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return st.nextToken();
        }

        int nextInt() {
            return Integer.parseInt(next());
        }

        long nextLong() {
            return Long.parseLong(next());
        }

        double nextDouble() {
            return Double.parseDouble(next());
        }

        String nextLine() {
            String str = "";
            try {
                str = br.readLine();
            } catch (IOException e) {
                e.printStackTrace();
            }
            return str;
        }
    }
    public static FastReader scn = new FastReader();

    public static void main(String[] args) {
        int n = scn.nextInt();
        int[] ii = new int[n*n+1] ,jj = new int[n*n+1];
        for(int i = 1; i<=n; i++){
            for(int j = 1; j<= n; j++){
                int a = scn.nextInt();
                ii[a] = i;
                jj[a] = j;
            }
        }
        int[][] mat = new int[n*n][3];
        for(int i = 1; i< n*n; i++){
            mat[i][0] = knight(n,ii[i],jj[i],ii[i+1],jj[i+1]);
            mat[i][1] = bishop(ii[i],jj[i],ii[i+1],jj[i+1]);
            mat[i][2] = rook(ii[i],jj[i],ii[i+1],jj[i+1]);
        }

//        System.out.println("YES");
        heapmover a = helper(mat,1,0, new heapmover[n*n+1][4]);
        heapmover b = helper(mat,1,1, new heapmover[n*n+1][4]);
        heapmover c = helper(mat,1,2, new heapmover[n*n+1][4]);

        PriorityQueue<heapmover> heap = new PriorityQueue<>();
        heap.add(a);
        heap.add(b);
        heap.add(c);

        heapmover res = heap.poll();
        System.out.println(res.value + " " + res.rep);
    }

    private static class heapmover implements Comparable<heapmover>{
        int value;
        int rep;

        @Override
        public int compareTo(heapmover o) {
            if(this.value==o.value) return this.rep - o.rep;
            return this.value - o.value;
        }
    }

    private static heapmover helper(int[][] mat, int i, int j, heapmover[][] strg){
        if(mat[i][j]==-1){
            heapmover hm = new heapmover();
            hm.value = 10000000;
            return strg[i][j] = hm;
        }
        if(i==mat.length-1) {
            heapmover hm = new heapmover();
            hm.value = mat[i][j];
            hm.rep = 0;
            return hm;
        }

        if(strg[i][j]!=null ) return strg[i][j];

        heapmover a = helper(mat,i+1,j,strg);
        heapmover b = helper(mat,i+1,(j+1)%3, strg);
        heapmover c = helper(mat,i+1,(j+2)%3, strg);

        heapmover aa = new heapmover();
        heapmover bb = new heapmover();
        heapmover cc = new heapmover();

        aa.value = a.value + mat[i][j];
        aa.rep = a.rep;
        bb.value = b.value + mat[i][j] + 1;
        bb.rep = b.rep + 1;
        cc.value = c.value + mat[i][j] + 1;
        cc.rep = c.rep + 1;

        PriorityQueue<heapmover> heap = new PriorityQueue<>();
        heap.add(aa);
        heap.add(bb);
        heap.add(cc);

        heapmover d = heap.poll();
        strg[i][j] = d;
        return d;
    }

    private static int rook(int is, int js, int id, int jd){
        if(is==id || jd==js) return 1;
        return 2;
    }

    private static int bishop(int is, int js, int id, int jd){
        if(Math.abs(is-id) == Math.abs(js-jd)) return 1;
        if((Math.abs(is-id)+Math.abs(js-jd))%2==0) return 2;
        return -1;
    }

    private static class cell{
        int x, y , distance;
    }

    private static int knight(int n , int is, int js, int id, int jd){
        return bfs(n,n,is,js,id,jd);
    }

    private static int bfs(int n ,int m , int r, int c, int d1, int d2){
        int[] a = {-2,-2,-1,-1,1,1,2,2};
        int[] b = {-1,1,-2,2,-2,2,-1,1};

        boolean[][] visited = new boolean[n+1][m+1];

        LinkedList<cell> queue = new LinkedList<>();
        cell start = new cell();
        start.distance = 0;
        start.x = r;
        start.y = c;
        queue.addLast(start);

        while (!queue.isEmpty()){
            cell t = queue.removeFirst();
            visited[t.x][t.y] = true;

            if(t.x==d1 && t.y==d2) return t.distance;

            for(int i = 0 ; i< 8 ; i++){
                int x = t.x + a[i];
                int y = t.y + b[i];

                if(check(n,m,x,y) && !visited[x][y]){
                    cell aa = new cell();
                    aa.x = x;
                    aa.y = y;
                    aa.distance = t.distance+1;
                    queue.addLast(aa);
                }
            }

        }

        return -1;
    }

    private static boolean check(int n , int m , int r , int c){
        if(r>n || r <=0 || c>m || c<=0) return false;
        return true;
    }
}

//package baobab;

import java.io.*;
import java.util.*;

public class D {

    public static void main(String[] args) {
        Solver solver = new Solver();
    }

    static class Solver {
        IO io;

        public Solver() {
            this.io = new IO();
            try {
                solve();
            } catch (RuntimeException e) {
                if (!e.getMessage().equals("Clean exit")) {
                    throw e;
                }
            } finally {
                io.close();
            }
        }

        /****************************** START READING HERE ********************************/

        void solve() {
            int n = io.nextInt();
            int[][] b = new int[n][n];
            for (int y=0; y<n; y++) {
                for (int x=0; x<n; x++) {
                    b[y][x] = io.nextInt();
                }
            }
            int end = n*n;
            int[] x = new int[end+1];
            int[] y = new int[end+1];
            for (int i=0; i<n; i++) {
                for (int j=0; j<n; j++) {
                    int v = b[i][j];
                    y[v] = i;
                    x[v] = j;
                }
            }

            State ans = null;
            State[][][][][] leastSteps = new State[n][n][n][n][3];
            Deque<State> q = new ArrayDeque<>();
            q.add(new State(y[1], x[1], y[2], x[2], ROOK, 0, 0));
            q.add(new State(y[1], x[1], y[2], x[2], KNIGHT, 0, 0));
            q.add(new State(y[1], x[1], y[2], x[2], BISHOP, 0, 0));
            while (!q.isEmpty()) {
                State s = q.pollFirst();
                State prev = leastSteps[s.currY][s.currX][s.goalY][s.goalX][s.piece];
                if (prev != null) {
                    if (prev.steps < s.steps) continue;
                    if (prev.steps == s.steps && prev.replacements <= s.replacements) continue;
                }
                leastSteps[s.currY][s.currX][s.goalY][s.goalX][s.piece] = s;

                // intermediary goal reached?
                if (s.currX == s.goalX && s.currY == s.goalY) {

                    if (b[s.currY][s.currX] == end) {
                        // actually reached end state with fewest steps but not necessarily fewest replacements for that step size
                        if (ans == null) ans = s;
                        else if (ans.replacements >= s.replacements) ans = s;
                        continue; // stop bfs but dont break because we may find new solution with same steps but fewer replacements
                    }

                    // change state to set next goal.
                    int prevGoal = b[s.goalY][s.goalX];
                    int newGoal = prevGoal + 1;
                    int newGoalX = x[newGoal];
                    int newGoalY = y[newGoal];
                    // front of the queue!
                    q.addFirst(new State(s.goalY, s.goalX, newGoalY, newGoalX, s.piece, s.steps, s.replacements));
                    continue;
                }

                // change piece?
                q.addLast(new State(s.currY, s.currX, s.goalY, s.goalX, ROOK, s.steps+1, s.replacements+1));
                q.addLast(new State(s.currY, s.currX, s.goalY, s.goalX, BISHOP, s.steps+1, s.replacements+1));
                q.addLast(new State(s.currY, s.currX, s.goalY, s.goalX, KNIGHT, s.steps+1, s.replacements+1));

                // move current piece?
                if (s.piece == ROOK) {
                    for (int newY=0; newY<n; newY++) q.addLast(new State(newY, s.currX, s.goalY, s.goalX, s.piece, s.steps+1, s.replacements));
                    for (int newX=0; newX<n; newX++) q.addLast(new State(s.currY, newX, s.goalY, s.goalX, s.piece, s.steps+1, s.replacements));
                } else if (s.piece == KNIGHT) {
                    int newY = s.currY + 1;
                    int newX = s.currX + 2;
                    if (newY >= 0 && newY < n && newX >= 0 && newX < n) {
                        q.addLast(new State(newY, newX, s.goalY, s.goalX, s.piece, s.steps+1, s.replacements));
                    }
                    newY = s.currY + 1;
                    newX = s.currX - 2;
                    if (newY >= 0 && newY < n && newX >= 0 && newX < n) {
                        q.addLast(new State(newY, newX, s.goalY, s.goalX, s.piece, s.steps+1, s.replacements));
                    }
                    newY = s.currY - 1;
                    newX = s.currX - 2;
                    if (newY >= 0 && newY < n && newX >= 0 && newX < n) {
                        q.addLast(new State(newY, newX, s.goalY, s.goalX, s.piece, s.steps+1, s.replacements));
                    }
                    newY = s.currY - 1;
                    newX = s.currX + 2;
                    if (newY >= 0 && newY < n && newX >= 0 && newX < n) {
                        q.addLast(new State(newY, newX, s.goalY, s.goalX, s.piece, s.steps+1, s.replacements));
                    }
                    newY = s.currY + 2;
                    newX = s.currX + 1;
                    if (newY >= 0 && newY < n && newX >= 0 && newX < n) {
                        q.addLast(new State(newY, newX, s.goalY, s.goalX, s.piece, s.steps+1, s.replacements));
                    }
                    newY = s.currY - 2;
                    newX = s.currX + 1;
                    if (newY >= 0 && newY < n && newX >= 0 && newX < n) {
                        q.addLast(new State(newY, newX, s.goalY, s.goalX, s.piece, s.steps+1, s.replacements));
                    }
                    newY = s.currY + 2;
                    newX = s.currX - 1;
                    if (newY >= 0 && newY < n && newX >= 0 && newX < n) {
                        q.addLast(new State(newY, newX, s.goalY, s.goalX, s.piece, s.steps+1, s.replacements));
                    }
                    newY = s.currY - 2;
                    newX = s.currX - 1;
                    if (newY >= 0 && newY < n && newX >= 0 && newX < n) {
                        q.addLast(new State(newY, newX, s.goalY, s.goalX, s.piece, s.steps+1, s.replacements));
                    }

                } else if (s.piece == BISHOP) {
                    int newY = s.currY;
                    int newX = s.currX;
                    int diff = 1;
                    while (true) {
                        newY += diff;
                        newX += diff;
                        if (newX >= n || newX < 0 || newY >= n || newY < 0) break;
                        q.addLast(new State(newY, newX, s.goalY, s.goalX, s.piece, s.steps+1, s.replacements));
                    }
                    newY = s.currY;
                    newX = s.currX;
                    diff = 1;
                    while (true) {
                        newY += diff;
                        newX -= diff;
                        if (newX >= n || newX < 0 || newY >= n || newY < 0) break;
                        q.addLast(new State(newY, newX, s.goalY, s.goalX, s.piece, s.steps+1, s.replacements));
                    }
                    newY = s.currY;
                    newX = s.currX;
                    diff = 1;
                    while (true) {
                        newY -= diff;
                        newX -= diff;
                        if (newX >= n || newX < 0 || newY >= n || newY < 0) break;
                        q.addLast(new State(newY, newX, s.goalY, s.goalX, s.piece, s.steps+1, s.replacements));
                    }
                    newY = s.currY;
                    newX = s.currX;
                    diff = 1;
                    while (true) {
                        newY -= diff;
                        newX += diff;
                        if (newX >= n || newX < 0 || newY >= n || newY < 0) break;
                        q.addLast(new State(newY, newX, s.goalY, s.goalX, s.piece, s.steps+1, s.replacements));
                    }
                }
            }

            io.println(ans.steps + " " + ans.replacements);
        }

        static int ROOK = 0;
        static int KNIGHT = 1;
        static int BISHOP = 2;

        class State {
            int currY;
            int currX;
            int goalY;
            int goalX;
            int piece;
            int steps;
            int replacements;

            public State(int currY, int currX, int goalY, int goalX, int piece, int steps, int replacements) {
                this.currY = currY;
                this.currX = currX;
                this.goalY = goalY;
                this.goalX = goalX;
                this.piece = piece;
                this.steps = steps;
                this.replacements = replacements;
            }

            @Override
            public boolean equals(Object o) {
                if (this == o) return true;
                if (o == null || getClass() != o.getClass()) return false;
                State state = (State) o;
                return currY == state.currY &&
                        currX == state.currX &&
                        goalY == state.goalY &&
                        goalX == state.goalX &&
                        piece == state.piece;
            }

            @Override
            public int hashCode() {
                return Objects.hash(currY, currX, goalY, goalX, piece);
            }
        }

        /************************** UTILITY CODE BELOW THIS LINE **************************/

        long MOD = (long)1e9 + 7;

        boolean closeToZero(double v) {
            // Check if double is close to zero, considering precision issues.
            return Math.abs(v) <= 0.0000000001;
        }

        class DrawGrid {

            void draw(boolean[][] d) {
                System.out.print("  ");
                for (int x=0; x<d[0].length; x++) {
                    System.out.print(" " + x + " ");
                }
                System.out.println("");
                for (int y=0; y<d.length; y++) {
                    System.out.print(y + " ");
                    for (int x=0; x<d[0].length; x++) {
                        System.out.print((d[y][x] ? "[x]" : "[ ]"));
                    }
                    System.out.println("");
                }
            }

            void draw(int[][] d) {
                int max = 1;
                for (int y=0; y<d.length; y++) {
                    for (int x=0; x<d[0].length; x++) {
                        max = Math.max(max, ("" + d[y][x]).length());
                    }
                }
                System.out.print("  ");
                String format = "%" + (max+2) + "s";
                for (int x=0; x<d[0].length; x++) {
                    System.out.print(String.format(format, x) + " ");
                }
                format = "%" + (max) + "s";
                System.out.println("");
                for (int y=0; y<d.length; y++) {
                    System.out.print(y + " ");
                    for (int x=0; x<d[0].length; x++) {
                        System.out.print(" [" + String.format(format, (d[y][x])) + "]");
                    }
                    System.out.println("");
                }
            }

        }

        class IDval implements Comparable<IDval> {
            int steps;
            long replacements;

            public IDval(int steps, long replacements) {
                this.replacements = replacements;
                this.steps = steps;
            }

            @Override
            public int compareTo(IDval o) {
                if (this.replacements < o.replacements) return -1;
                if (this.replacements > o.replacements) return 1;
                return this.steps - o.steps;
            }
        }

        private class ElementCounter {
            private HashMap<Long, Integer> elements;

            public ElementCounter() {
                elements = new HashMap<>();
            }

            public void add(long element) {
                int count = 1;
                Integer prev = elements.get(element);
                if (prev != null) count += prev;
                elements.put(element, count);
            }

            public void remove(long element) {
                int count = elements.remove(element);
                count--;
                if (count > 0) elements.put(element, count);
            }

            public int get(long element) {
                Integer val = elements.get(element);
                if (val == null) return 0;
                return val;
            }

            public int size() {
                return elements.size();
            }
        }

        class StringCounter {
            HashMap<String, Integer> elements;

            public StringCounter() {
                elements = new HashMap<>();
            }

            public void add(String identifier) {
                int count = 1;
                Integer prev = elements.get(identifier);
                if (prev != null) count += prev;
                elements.put(identifier, count);
            }

            public void remove(String identifier) {
                int count = elements.remove(identifier);
                count--;
                if (count > 0) elements.put(identifier, count);
            }

            public long get(String identifier) {
                Integer val = elements.get(identifier);
                if (val == null) return 0;
                return val;
            }

            public int size() {
                return elements.size();
            }
        }

        class DisjointSet {
            /** Union Find / Disjoint Set data structure. */
            int[] size;
            int[] parent;
            int componentCount;

            public DisjointSet(int n) {
                componentCount = n;
                size = new int[n];
                parent = new int[n];
                for (int i=0; i<n; i++) parent[i] = i;
                for (int i=0; i<n; i++) size[i] = 1;
            }

            public void join(int a, int b) {
                /* Find roots */
                int rootA = parent[a];
                int rootB = parent[b];
                while (rootA != parent[rootA]) rootA = parent[rootA];
                while (rootB != parent[rootB]) rootB = parent[rootB];

                if (rootA == rootB) {
                    /* Already in the same set */
                    return;
                }

                /* Merge smaller set into larger set. */
                if (size[rootA] > size[rootB]) {
                    size[rootA] += size[rootB];
                    parent[rootB] = rootA;
                } else {
                    size[rootB] += size[rootA];
                    parent[rootA] = rootB;
                }
                componentCount--;
            }

        }

        class Trie {
            int N;
            int Z;
            int nextFreeId;
            int[][] pointers;
            boolean[] end;

            /** maxLenSum = maximum possible sum of length of words */
            public Trie(int maxLenSum, int alphabetSize) {
                this.N = maxLenSum;
                this.Z = alphabetSize;
                this.nextFreeId = 1;
                pointers = new int[N+1][alphabetSize];
                end = new boolean[N+1];
            }

            public void addWord(String word) {
                int curr = 0;
                for (int j=0; j<word.length(); j++) {
                    int c = word.charAt(j) - 'a';
                    int next = pointers[curr][c];
                    if (next == 0) {
                        next = nextFreeId++;
                        pointers[curr][c] = next;
                    }
                    curr = next;
                }
                end[curr] = true;
            }

            public boolean hasWord(String word) {
                int curr = 0;
                for (int j=0; j<word.length(); j++) {
                    int c = word.charAt(j) - 'a';
                    int next = pointers[curr][c];
                    if (next == 0) return false;
                    curr = next;
                }
                return end[curr];
            }

        }

        private static class Prob {

            /** For heavy calculations on probabilities, this class
             *  provides more accuracy & efficiency than doubles.
             *  Math explained: https://en.wikipedia.org/wiki/Log_probability
             *  Quick start:
             *      - Instantiate probabilities, eg. Prob a = new Prob(0.75)
             *      - add(), multiply() return new objects, can perform on nulls & NaNs.
             *      - get() returns probability as a readable double */

            /** Logarithmized probability. Note: 0% represented by logP NaN. */
            private double logP;

            /** Construct instance with real probability. */
            public Prob(double real) {
                if (real > 0) this.logP = Math.log(real);
                else this.logP = Double.NaN;
            }

            /** Construct instance with already logarithmized value. */
            static boolean dontLogAgain = true;
            public Prob(double logP, boolean anyBooleanHereToChooseThisConstructor) {
                this.logP = logP;
            }

            /** Returns real probability as a double. */
            public double get() {
                return Math.exp(logP);
            }

            @Override
            public String toString() {
                return ""+get();
            }

            /***************** STATIC METHODS BELOW ********************/

            /** Note: returns NaN only when a && b are both NaN/null. */
            public static Prob add(Prob a, Prob b) {
                if (nullOrNaN(a) && nullOrNaN(b)) return new Prob(Double.NaN, dontLogAgain);
                if (nullOrNaN(a)) return copy(b);
                if (nullOrNaN(b)) return copy(a);

                double x = Math.max(a.logP, b.logP);
                double y = Math.min(a.logP, b.logP);
                double sum = x + Math.log(1 + Math.exp(y - x));
                return new Prob(sum, dontLogAgain);
            }

            /** Note: multiplying by null or NaN produces NaN (repping 0% real prob). */
            public static Prob multiply(Prob a, Prob b) {
                if (nullOrNaN(a) || nullOrNaN(b)) return new Prob(Double.NaN, dontLogAgain);
                return new Prob(a.logP + b.logP, dontLogAgain);
            }

            /** Returns true if p is null or NaN. */
            private static boolean nullOrNaN(Prob p) {
                return (p == null || Double.isNaN(p.logP));
            }

            /** Returns a new instance with the same value as original. */
            private static Prob copy(Prob original) {
                return new Prob(original.logP, dontLogAgain);
            }
        }

        class Binary implements Comparable<Binary> {

            /**
             * Use example: Binary b = new Binary(Long.toBinaryString(53249834L));
             *
             * When manipulating small binary strings, instantiate new Binary(string)
             * When just reading large binary strings, instantiate new Binary(string,true)
             * get(int i) returns a character '1' or '0', not an int.
             */

            private boolean[] d;
            private int first; // Starting from left, the first (most remarkable) '1'
            public int length;

            public Binary(String binaryString) {
                this(binaryString, false);
            }
            public Binary(String binaryString, boolean initWithMinArraySize) {
                length = binaryString.length();
                int size = Math.max(2*length, 1);
                first = length/4;
                if (initWithMinArraySize) {
                    first = 0;
                    size = Math.max(length, 1);
                }
                d = new boolean[size];
                for (int i=0; i<length; i++) {
                    if (binaryString.charAt(i) == '1') d[i+first] = true;
                }
            }

            public void addFirst(char c) {
                if (first-1 < 0) doubleArraySize();
                first--;
                d[first] = (c == '1' ? true : false);
                length++;
            }

            public void addLast(char c) {
                if (first+length >= d.length) doubleArraySize();
                d[first+length] = (c == '1' ? true : false);
                length++;
            }

            private void doubleArraySize() {
                boolean[] bigArray = new boolean[(d.length+1) * 2];
                int newFirst = bigArray.length / 4;
                for (int i=0; i<length; i++) {
                    bigArray[i + newFirst] = d[i + first];
                }
                first = newFirst;
                d = bigArray;
            }

            public boolean flip(int i) {
                boolean value = (this.d[first+i] ? false : true);
                this.d[first+i] = value;
                return value;
            }

            public void set(int i, char c) {
                boolean value = (c == '1' ? true : false);
                this.d[first+i] = value;
            }

            public char get(int i) {
                return (this.d[first+i] ? '1' : '0');
            }

            @Override
            public int compareTo(Binary o) {
                if (this.length != o.length) return this.length - o.length;
                int len = this.length;
                for (int i=0; i<len; i++) {
                    int diff = this.get(i) - o.get(i);
                    if (diff != 0) return diff;
                }
                return 0;
            }

            @Override
            public String toString() {
                StringBuilder sb = new StringBuilder();
                for (int i=0; i<length; i++) {
                    sb.append(d[i+first] ? '1' : '0');
                }
                return sb.toString();
            }

        }

        /************************** Range queries **************************/

        class FenwickMin {
            long n;
            long[] original;
            long[] bottomUp;
            long[] topDown;

            public FenwickMin(int n) {
                this.n = n;
                original = new long[n+2];
                bottomUp = new long[n+2];
                topDown = new long[n+2];
            }

            public void set(int modifiedNode, long value) {
                long replaced = original[modifiedNode];
                original[modifiedNode] = value;
                // Update left tree
                int i = modifiedNode;
                long v = value;
                while (i <= n) {
                    if (v > bottomUp[i]) {
                        if (replaced == bottomUp[i]) {
                            v = Math.min(v, original[i]);
                            for (int r=1 ;; r++) {
                                int x = (i&-i)>>>r;
                                if (x == 0) break;
                                int child = i-x;
                                v = Math.min(v, bottomUp[child]);
                            }
                        } else break;
                    }
                    if (v == bottomUp[i]) break;
                    bottomUp[i] = v;
                    i += (i&-i);
                }
                // Update right tree
                i = modifiedNode;
                v = value;
                while (i > 0) {
                    if (v > topDown[i]) {
                        if (replaced == topDown[i]) {
                            v = Math.min(v, original[i]);
                            for (int r=1 ;; r++) {
                                int x = (i&-i)>>>r;
                                if (x == 0) break;
                                int child = i+x;
                                if (child > n+1) break;
                                v = Math.min(v, topDown[child]);
                            }
                        } else break;
                    }
                    if (v == topDown[i]) break;
                    topDown[i] = v;
                    i -= (i&-i);
                }
            }

            public long getMin(int a, int b) {
                long min = original[a];
                int prev = a;
                int curr = prev + (prev&-prev); // parent right hand side
                while (curr <= b) {
                    min = Math.min(min, topDown[prev]); // value from the other tree
                    prev = curr;
                    curr = prev + (prev&-prev);;
                }
                min = Math.min(min, original[prev]);
                prev = b;
                curr = prev - (prev&-prev); // parent left hand side
                while (curr >= a) {
                    min = Math.min(min,bottomUp[prev]); // value from the other tree
                    prev = curr;
                    curr = prev - (prev&-prev);
                }
                return min;
            }

        }

        class FenwickSum {
            public long[] d;

            public FenwickSum(int n) {
                d=new long[n+1];
            }

            /** a[0] must be unused. */
            public FenwickSum(long[] a) {
                d=new long[a.length];
                for (int i=1; i<a.length; i++) {
                    modify(i, a[i]);
                }
            }

            /** Do not modify i=0. */
            void modify(int i, long v) {
                while (i<d.length) {
                    d[i] += v;
                    // Move to next uplink on the RIGHT side of i
                    i += (i&-i);
                }
            }

            /** Returns sum from a to b, *BOTH* inclusive. */
            long getSum(int a, int b) {
                return getSum(b) - getSum(a-1);
            }

            private long getSum(int i) {
                long sum = 0;
                while (i>0) {
                    sum += d[i];
                    // Move to next uplink on the LEFT side of i
                    i -= (i&-i);
                }
                return sum;
            }
        }

        class SegmentTree {

            /* Provides log(n) operations for:
             * - Range query (sum, min or max)
             * - Range update ("+8 to all values between indexes 4 and 94")
             */

            int N;
            long[] lazy;
            long[] sum;
            long[] min;
            long[] max;
            boolean supportSum;
            boolean supportMin;
            boolean supportMax;

            public SegmentTree(int n) {
                this(n, true, true, true);
            }

            public SegmentTree(int n, boolean supportSum, boolean supportMin, boolean supportMax) {
                for (N=2; N<n;) N*=2;
                this.lazy = new long[2*N];
                this.supportSum = supportSum;
                this.supportMin = supportMin;
                this.supportMax = supportMax;
                if (this.supportSum) this.sum = new long[2*N];
                if (this.supportMin) this.min = new long[2*N];
                if (this.supportMax) this.max = new long[2*N];
            }

            void modifyRange(long x, int a, int b) {
                modifyRec(a, b, 1, 0, N-1, x);
            }

            void setRange() {
                //TODO
            }

            long getSum(int a, int b) {
                return querySum(a, b, 1, 0, N-1);
            }

            long getMin(int a, int b) {
                return queryMin(a, b, 1, 0, N-1);
            }

            long getMax(int a, int b) {
                return queryMax(a, b, 1, 0, N-1);
            }

            private long querySum(int wantedLeft, int wantedRight, int i, int actualLeft, int actualRight) {
                if (wantedLeft > actualRight || wantedRight < actualLeft) {
                    return 0;
                }
                if (wantedLeft == actualLeft && wantedRight == actualRight) {
                    int count = wantedRight - wantedLeft + 1;
                    return sum[i] + count * lazy[i];
                }
                if (lazy[i] != 0) propagate(i, actualLeft, actualRight);
                int d = (actualRight - actualLeft + 1) / 2;
                long left = querySum(wantedLeft, min(actualLeft+d-1, wantedRight), 2*i, actualLeft, actualLeft+d-1);
                long right = querySum(max(actualLeft+d, wantedLeft), wantedRight, 2*i+1, actualLeft+d, actualRight);
                return left + right;
            }

            private long queryMin(int wantedLeft, int wantedRight, int i, int actualLeft, int actualRight) {
                if (wantedLeft > actualRight || wantedRight < actualLeft) {
                    return 0;
                }
                if (wantedLeft == actualLeft && wantedRight == actualRight) {
                    return min[i] + lazy[i];
                }
                if (lazy[i] != 0) propagate(i, actualLeft, actualRight);
                int d = (actualRight - actualLeft + 1) / 2;
                long left = queryMin(wantedLeft, min(actualLeft+d-1, wantedRight), 2*i, actualLeft, actualLeft+d-1);
                long right = queryMin(max(actualLeft+d, wantedLeft), wantedRight, 2*i+1, actualLeft+d, actualRight);
                return min(left, right);
            }

            private long queryMax(int wantedLeft, int wantedRight, int i, int actualLeft, int actualRight) {
                if (wantedLeft > actualRight || wantedRight < actualLeft) {
                    return 0;
                }
                if (wantedLeft == actualLeft && wantedRight == actualRight) {
                    return max[i] + lazy[i];
                }
                if (lazy[i] != 0) propagate(i, actualLeft, actualRight);
                int d = (actualRight - actualLeft + 1) / 2;
                long left = queryMax(wantedLeft, min(actualLeft+d-1, wantedRight), 2*i, actualLeft, actualLeft+d-1);
                long right = queryMax(max(actualLeft+d, wantedLeft), wantedRight, 2*i+1, actualLeft+d, actualRight);
                return max(left, right);
            }

            private void modifyRec(int wantedLeft, int wantedRight, int i, int actualLeft, int actualRight, long value) {
                if (wantedLeft > actualRight || wantedRight < actualLeft) {
                    return;
                }
                if (wantedLeft == actualLeft && wantedRight == actualRight) {
                    lazy[i] += value;
                    return;
                }
                if (lazy[i] != 0) propagate(i, actualLeft, actualRight);
                int d = (actualRight - actualLeft + 1) / 2;
                modifyRec(wantedLeft, min(actualLeft+d-1, wantedRight), 2*i, actualLeft, actualLeft+d-1, value);
                modifyRec(max(actualLeft+d, wantedLeft), wantedRight, 2*i+1, actualLeft+d, actualRight, value);
                if (supportSum) sum[i] += value * (min(actualRight, wantedRight) - max(actualLeft, wantedLeft) + 1);
                if (supportMin) min[i] = min(min[2*i] + lazy[2*i], min[2*i+1] + lazy[2*i+1]);
                if (supportMax) max[i] = max(max[2*i] + lazy[2*i], max[2*i+1] + lazy[2*i+1]);
            }

            private void propagate(int i, int actualLeft, int actualRight) {
                lazy[2*i] += lazy[i];
                lazy[2*i+1] += lazy[i];
                if (supportSum) sum[i] += lazy[i] * (actualRight - actualLeft + 1);
                if (supportMin) min[i] += lazy[i];
                if (supportMax) max[i] += lazy[i];
                lazy[i] = 0;
            }

        }

        /***************************** Graphs *****************************/

        List<Integer>[] toGraph(IO io, int n) {
            List<Integer>[] g = new ArrayList[n+1];
            for (int i=1; i<=n; i++) g[i] = new ArrayList<>();
            for (int i=1; i<=n-1; i++) {
                int a = io.nextInt();
                int b = io.nextInt();
                g[a].add(b);
                g[b].add(a);
            }
            return g;
        }

        class Graph {
            HashMap<Long, List<Long>> edges;

            public Graph() {
                edges = new HashMap<>();
            }

            List<Long> getSetNeighbors(Long node) {
                List<Long> neighbors = edges.get(node);
                if (neighbors == null) {
                    neighbors = new ArrayList<>();
                    edges.put(node, neighbors);
                }
                return neighbors;
            }

            void addBiEdge(Long a, Long b) {
                addEdge(a, b);
                addEdge(b, a);
            }

            void addEdge(Long from, Long to) {
                getSetNeighbors(to); // make sure all have initialized lists
                List<Long> neighbors = getSetNeighbors(from);
                neighbors.add(to);
            }

            // topoSort variables
            int UNTOUCHED = 0;
            int FINISHED = 2;
            int INPROGRESS = 1;
            HashMap<Long, Integer> vis;
            List<Long> topoAns;
            List<Long> failDueToCycle = new ArrayList<Long>() {{ add(-1L); }};

            List<Long> topoSort() {
                topoAns = new ArrayList<>();
                vis = new HashMap<>();
                for (Long a : edges.keySet()) {
                    if (!topoDFS(a)) return failDueToCycle;
                }
                Collections.reverse(topoAns);
                return topoAns;
            }

            boolean topoDFS(long curr) {
                Integer status = vis.get(curr);
                if (status == null) status = UNTOUCHED;
                if (status == FINISHED) return true;
                if (status == INPROGRESS) {
                    return false;
                }
                vis.put(curr, INPROGRESS);
                for (long next : edges.get(curr)) {
                    if (!topoDFS(next)) return false;
                }
                vis.put(curr, FINISHED);
                topoAns.add(curr);
                return true;
            }

        }

        public class StronglyConnectedComponents {

            /** Kosaraju's algorithm */

            ArrayList<Integer>[] forw;
            ArrayList<Integer>[] bacw;

            /** Use: getCount(2, new int[] {1,2}, new int[] {2,1}) */
            public int getCount(int n, int[] mista, int[] minne) {
                forw = new ArrayList[n+1];
                bacw = new ArrayList[n+1];
                for (int i=1; i<=n; i++) {
                    forw[i] = new ArrayList<Integer>();
                    bacw[i] = new ArrayList<Integer>();
                }
                for (int i=0; i<mista.length; i++) {
                    int a = mista[i];
                    int b = minne[i];
                    forw[a].add(b);
                    bacw[b].add(a);
                }
                int count = 0;
                List<Integer> list = new ArrayList<Integer>();
                boolean[] visited = new boolean[n+1];
                for (int i=1; i<=n; i++) {
                    dfsForward(i, visited, list);
                }
                visited = new boolean[n+1];
                for (int i=n-1; i>=0; i--) {
                    int node = list.get(i);
                    if (visited[node]) continue;
                    count++;
                    dfsBackward(node, visited);
                }
                return count;
            }

            public void dfsForward(int i, boolean[] visited, List<Integer> list) {
                if (visited[i]) return;
                visited[i] = true;
                for (int neighbor : forw[i]) {
                    dfsForward(neighbor, visited, list);
                }
                list.add(i);
            }

            public void dfsBackward(int i, boolean[] visited) {
                if (visited[i]) return;
                visited[i] = true;
                for (int neighbor : bacw[i]) {
                    dfsBackward(neighbor, visited);
                }
            }
        }

        class LCAFinder {

            /* O(n log n) Initialize: new LCAFinder(graph)
             * O(log n) Queries: find(a,b) returns lowest common ancestor for nodes a and b */

            int[] nodes;
            int[] depths;
            int[] entries;
            int pointer;
            FenwickMin fenwick;

            public LCAFinder(List<Integer>[] graph) {
                this.nodes = new int[(int)10e6];
                this.depths = new int[(int)10e6];
                this.entries = new int[graph.length];
                this.pointer = 1;
                boolean[] visited = new boolean[graph.length+1];
                dfs(1, 0, graph, visited);
                fenwick = new FenwickMin(pointer-1);
                for (int i=1; i<pointer; i++) {
                    fenwick.set(i, depths[i] * 1000000L + i);
                }
            }

            private void dfs(int node, int depth, List<Integer>[] graph, boolean[] visited) {
                visited[node] = true;
                entries[node] = pointer;
                nodes[pointer] = node;
                depths[pointer] = depth;
                pointer++;
                for (int neighbor : graph[node]) {
                    if (visited[neighbor]) continue;
                    dfs(neighbor, depth+1, graph, visited);
                    nodes[pointer] = node;
                    depths[pointer] = depth;
                    pointer++;
                }
            }

            public int find(int a, int b) {
                int left = entries[a];
                int right = entries[b];
                if (left > right) {
                    int temp = left;
                    left = right;
                    right = temp;
                }
                long mixedBag = fenwick.getMin(left, right);
                int index = (int) (mixedBag % 1000000L);
                return nodes[index];
            }
        }

        /**************************** Geometry ****************************/

        class Point {
            int y;
            int x;

            public Point(int y, int x) {
                this.y = y;
                this.x = x;
            }
        }

        boolean segmentsIntersect(double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4) {
            // Returns true if segment 1-2 intersects segment 3-4

            if (x1 == x2 && x3 == x4) {
                // Both segments are vertical
                if (x1 != x3) return false;
                if (min(y1,y2) < min(y3,y4)) {
                    return max(y1,y2) >= min(y3,y4);
                } else {
                    return max(y3,y4) >= min(y1,y2);
                }
            }
            if (x1 == x2) {
                // Only segment 1-2 is vertical. Does segment 3-4 cross it? y = a*x + b
                double a34 = (y4-y3)/(x4-x3);
                double b34 = y3 - a34*x3;
                double y = a34 * x1 + b34;
                return y >= min(y1,y2) && y <= max(y1,y2) && x1 >= min(x3,x4) && x1 <= max(x3,x4);
            }
            if (x3 == x4) {
                // Only segment 3-4 is vertical. Does segment 1-2 cross it? y = a*x + b
                double a12 = (y2-y1)/(x2-x1);
                double b12 = y1 - a12*x1;
                double y = a12 * x3 + b12;
                return y >= min(y3,y4) && y <= max(y3,y4) && x3 >= min(x1,x2) && x3 <= max(x1,x2);
            }
            double a12 = (y2-y1)/(x2-x1);
            double b12 = y1 - a12*x1;
            double a34 = (y4-y3)/(x4-x3);
            double b34 = y3 - a34*x3;
            if (closeToZero(a12 - a34)) {
                // Parallel lines
                return closeToZero(b12 - b34);
            }
            // Non parallel non vertical lines intersect at x. Is x part of both segments?
            double x = -(b12-b34)/(a12-a34);
            return x >= min(x1,x2) && x <= max(x1,x2) && x >= min(x3,x4) && x <= max(x3,x4);
        }

        boolean pointInsideRectangle(Point p, List<Point> r, boolean countBorderAsInside) {
            Point a = r.get(0);
            Point b = r.get(1);
            Point c = r.get(2);
            Point d = r.get(3);
            double apd = areaOfTriangle(a, p, d);
            double dpc = areaOfTriangle(d, p, c);
            double cpb = areaOfTriangle(c, p, b);
            double pba = areaOfTriangle(p, b, a);
            double sumOfAreas = apd + dpc + cpb + pba;
            if (closeToZero(sumOfAreas - areaOfRectangle(r))) {
                if (closeToZero(apd) || closeToZero(dpc) || closeToZero(cpb) || closeToZero(pba)) {
                    return countBorderAsInside;
                }
                return true;
            }
            return false;
        }

        double areaOfTriangle(Point a, Point b, Point c) {
            return 0.5 * Math.abs((a.x-c.x)*(b.y-a.y)-(a.x-b.x)*(c.y-a.y));
        }

        double areaOfRectangle(List<Point> r) {
            double side1xDiff = r.get(0).x - r.get(1).x;
            double side1yDiff = r.get(0).y - r.get(1).y;
            double side2xDiff = r.get(1).x - r.get(2).x;
            double side2yDiff = r.get(1).y - r.get(2).y;
            double side1 = Math.sqrt(side1xDiff * side1xDiff + side1yDiff * side1yDiff);
            double side2 = Math.sqrt(side2xDiff * side2xDiff + side2yDiff * side2yDiff);
            return side1 * side2;
        }

        boolean pointsOnSameLine(double x1, double y1, double x2, double y2, double x3, double y3) {
            double areaTimes2 = x1 * (y2 - y3) + x2 * (y3 - y1) + x3 * (y1 - y2);
            return (closeToZero(areaTimes2));
        }

        class PointToLineSegmentDistanceCalculator {

            // Just call this
            double minDistFromPointToLineSegment(double point_x, double point_y, double x1, double y1, double x2, double y2) {
                return Math.sqrt(distToSegmentSquared(point_x, point_y, x1, y1, x2, y2));
            }

            private double distToSegmentSquared(double point_x, double point_y, double x1, double y1, double x2, double y2) {
                double l2 = dist2(x1,y1,x2,y2);
                if (l2 == 0) return dist2(point_x, point_y, x1, y1);
                double t = ((point_x - x1) * (x2 - x1) + (point_y - y1) * (y2 - y1)) / l2;
                if (t < 0) return dist2(point_x, point_y, x1, y1);
                if (t > 1) return dist2(point_x, point_y, x2, y2);

                double com_x = x1 + t * (x2 - x1);
                double com_y = y1 + t * (y2 - y1);
                return dist2(point_x, point_y, com_x, com_y);
            }

            private double dist2(double x1, double y1, double x2, double y2) {
                return Math.pow((x1 - x2), 2) + Math.pow((y1 - y2), 2);
            }

        }

        /****************************** Math ******************************/

        long pow(long base, int exp) {
            if (exp == 0) return 1L;
            long x = pow(base, exp/2);
            long ans = x * x;
            if (exp % 2 != 0) ans *= base;
            return ans;
        }

        long gcd(long... v) {
            /** Chained calls to Euclidean algorithm. */
            if (v.length == 1) return v[0];
            long ans = gcd(v[1], v[0]);
            for (int i=2; i<v.length; i++) {
                ans = gcd(ans, v[i]);
            }
            return ans;
        }

        long gcd(long a, long b) {
            /** Euclidean algorithm. */
            if (b == 0) return a;
            return gcd(b, a%b);
        }

        int[] generatePrimesUpTo(int last) {
            /* Sieve of Eratosthenes. Practically O(n). Values of 0 indicate primes. */
            int[] div = new int[last+1];
            for (int x=2; x<=last; x++) {
                if (div[x] > 0) continue;
                for (int u=2*x; u<=last; u+=x) {
                    div[u] = x;
                }
            }
            return div;
        }

        long lcm(long a, long b) {
            /** Least common multiple */
            return a * b / gcd(a,b);
        }

        class BaseConverter {

            /* Palauttaa luvun esityksen kannassa base */
            public String convert(Long number, int base) {
                return Long.toString(number, base);
            }

            /* Palauttaa luvun esityksen kannassa baseTo, kun annetaan luku Stringinä kannassa baseFrom */
            public String convert(String number, int baseFrom, int baseTo) {
                return Long.toString(Long.parseLong(number, baseFrom), baseTo);
            }

            /* Tulkitsee kannassa base esitetyn luvun longiksi (kannassa 10) */
            public long longify(String number, int baseFrom) {
                return Long.parseLong(number, baseFrom);
            }
        }

        class BinomialCoefficients {
            /** Total number of K sized unique combinations from pool of size N (unordered)
             N! / ( K! (N - K)! )   */

            /** For simple queries where output fits in long. */
            public long biCo(long n, long k) {
                long r = 1;
                if (k > n) return 0;
                for (long d = 1; d <= k; d++) {
                    r *= n--;
                    r /= d;
                }
                return r;
            }

            /** For multiple queries with same n, different k. */
            public long[] precalcBinomialCoefficientsK(int n, int maxK) {
                long v[] = new long[maxK+1];
                v[0] = 1; // nC0 == 1
                for (int i=1; i<=n; i++) {
                    for (int j=Math.min(i,maxK); j>0; j--) {
                        v[j] = v[j] + v[j-1]; // Pascal's triangle
                    }
                }
                return v;
            }

            /** When output needs % MOD. */
            public long[] precalcBinomialCoefficientsK(int n, int k, long M) {
                long v[] = new long[k+1];
                v[0] = 1; // nC0 == 1
                for (int i=1; i<=n; i++) {
                    for (int j=Math.min(i,k); j>0; j--) {
                        v[j] = v[j] + v[j-1]; // Pascal's triangle
                        v[j] %= M;
                    }
                }
                return v;
            }
        }

        /**************************** Strings ****************************/

        class Zalgo {

            public int pisinEsiintyma(String haku, String kohde) {
                char[] s = new char[haku.length() + 1 + kohde.length()];
                for (int i=0; i<haku.length(); i++) {
                    s[i] = haku.charAt(i);
                }
                int j = haku.length();
                s[j++] = '#';
                for (int i=0; i<kohde.length(); i++) {
                    s[j++] = kohde.charAt(i);
                }
                int[] z = toZarray(s);
                int max = 0;
                for (int i=haku.length(); i<z.length; i++) {
                    max = Math.max(max, z[i]);
                }
                return max;
            }

            public int[] toZarray(char[] s) {
                int n = s.length;
                int[] z = new int[n];
                int a = 0, b = 0;
                for (int i = 1; i < n; i++) {
                    if (i > b) {
                        for (int j = i; j < n && s[j - i] == s[j]; j++)  z[i]++;
                    }
                    else {
                        z[i] = z[i - a];
                        if (i + z[i - a] > b) {
                            for (int j = b + 1; j < n && s[j - i] == s[j]; j++) z[i]++;
                            a = i;
                            b = i + z[i] - 1;
                        }
                    }
                }
                return z;
            }

            public List<Integer> getStartIndexesWhereWordIsFound(String haku, String kohde) {
                // this is alternative use case
                char[] s = new char[haku.length() + 1 + kohde.length()];
                for (int i=0; i<haku.length(); i++) {
                    s[i] = haku.charAt(i);
                }
                int j = haku.length();
                s[j++] = '#';
                for (int i=0; i<kohde.length(); i++) {
                    s[j++] = kohde.charAt(i);
                }
                int[] z = toZarray(s);
                List<Integer> indexes = new ArrayList<>();
                for (int i=haku.length(); i<z.length; i++) {
                    if (z[i] < haku.length()) continue;
                    indexes.add(i);
                }
                return indexes;
            }

        }

        class StringHasher {

            class HashedString {
                long[] hashes;
                long[] modifiers;

                public HashedString(long[] hashes, long[] modifiers) {
                    this.hashes = hashes;
                    this.modifiers = modifiers;
                }
            }

            long P;
            long M;

            public StringHasher() {
                initializePandM();
            }

            HashedString hashString(String s) {
                int n = s.length();
                long[] hashes = new long[n];
                long[] modifiers = new long[n];
                hashes[0] = s.charAt(0);
                modifiers[0] = 1;
                for (int i=1; i<n; i++) {
                    hashes[i] = (hashes[i-1] * P + s.charAt(i)) % M;
                    modifiers[i] = (modifiers[i-1] * P) % M;
                }
                return new HashedString(hashes, modifiers);
            }

            /**
             * Indices are inclusive.
             */
            long getHash(HashedString hashedString, int startIndex, int endIndex) {
                long[] hashes = hashedString.hashes;
                long[] modifiers = hashedString.modifiers;
                long result = hashes[endIndex];
                if (startIndex > 0) result -= (hashes[startIndex-1] * modifiers[endIndex-startIndex+1]) % M;
                if (result < 0) result += M;
                return result;
            }

            // Less interesting methods below

            /**
             * Efficient for 2 input parameter strings in particular.
             */
            HashedString[] hashString(String first, String second) {
                HashedString[] array = new HashedString[2];
                int n = first.length();
                long[] modifiers = new long[n];
                modifiers[0] = 1;

                long[] firstHashes = new long[n];
                firstHashes[0] = first.charAt(0);
                array[0] = new HashedString(firstHashes, modifiers);

                long[] secondHashes = new long[n];
                secondHashes[0] = second.charAt(0);
                array[1] = new HashedString(secondHashes, modifiers);

                for (int i=1; i<n; i++) {
                    modifiers[i] = (modifiers[i-1] * P) % M;
                    firstHashes[i] = (firstHashes[i-1] * P + first.charAt(i)) % M;
                    secondHashes[i] = (secondHashes[i-1] * P + second.charAt(i)) % M;
                }
                return array;
            }

            /**
             * Efficient for 3+ strings
             * More efficient than multiple hashString calls IF strings are same length.
             */
            HashedString[] hashString(String... strings) {
                HashedString[] array = new HashedString[strings.length];
                int n = strings[0].length();
                long[] modifiers = new long[n];
                modifiers[0] = 1;
                for (int j=0; j<strings.length; j++) {
                    // if all strings are not same length, defer work to another method
                    if (strings[j].length() != n) {
                        for (int i=0; i<n; i++) {
                            array[i] = hashString(strings[i]);
                        }
                        return array;
                    }

                    // otherwise initialize stuff
                    long[] hashes = new long[n];
                    hashes[0] = strings[j].charAt(0);
                    array[j] = new HashedString(hashes, modifiers);
                }
                for (int i=1; i<n; i++) {
                    modifiers[i] = (modifiers[i-1] * P) % M;
                    for (int j=0; j<strings.length; j++) {
                        String s = strings[j];
                        long[] hashes = array[j].hashes;
                        hashes[i] = (hashes[i-1] * P + s.charAt(i)) % M;
                    }
                }
                return array;
            }

            void initializePandM() {
                ArrayList<Long> modOptions = new ArrayList<>(20);
                modOptions.add(353873237L);
                modOptions.add(353875897L);
                modOptions.add(353878703L);
                modOptions.add(353882671L);
                modOptions.add(353885303L);
                modOptions.add(353888377L);
                modOptions.add(353893457L);
                P = modOptions.get(new Random().nextInt(modOptions.size()));

                modOptions.clear();
                modOptions.add(452940277L);
                modOptions.add(452947687L);
                modOptions.add(464478431L);
                modOptions.add(468098221L);
                modOptions.add(470374601L);
                modOptions.add(472879717L);
                modOptions.add(472881973L);
                M = modOptions.get(new Random().nextInt(modOptions.size()));

            }
        }

        int editDistance(String a, String b) {
            a = "#"+a;
            b = "#"+b;
            int n = a.length();
            int m = b.length();
            int[][] dp = new int[n+1][m+1];
            for (int y=0; y<=n; y++) {
                for (int x=0; x<=m; x++) {
                    if (y == 0) dp[y][x] = x;
                    else if (x == 0) dp[y][x] = y;
                    else {
                        int e1 = dp[y-1][x] + 1;
                        int e2 = dp[y][x-1] + 1;
                        int e3 = dp[y-1][x-1] + (a.charAt(y-1) != b.charAt(x-1) ? 1 : 0);
                        dp[y][x] = min(e1, e2, e3);
                    }
                }
            }
            return dp[n][m];
        }

        /*************************** Technical ***************************/

        private class IO extends PrintWriter {
            private InputStreamReader r;
            private static final int BUFSIZE = 1 << 15;
            private char[] buf;
            private int bufc;
            private int bufi;
            private StringBuilder sb;

            public IO() {
                super(new BufferedOutputStream(System.out));
                r = new InputStreamReader(System.in);

                buf = new char[BUFSIZE];
                bufc = 0;
                bufi = 0;
                sb = new StringBuilder();
            }

            /** Print, flush, return nextInt. */
            private int queryInt(String s) {
                io.println(s);
                io.flush();
                return nextInt();
            }

            /** Print, flush, return nextLong. */
            private long queryLong(String s) {
                io.println(s);
                io.flush();
                return nextLong();
            }

            /** Print, flush, return next word. */
            private String queryNext(String s) {
                io.println(s);
                io.flush();
                return next();
            }

            private void fillBuf() throws IOException {
                bufi = 0;
                bufc = 0;
                while(bufc == 0) {
                    bufc = r.read(buf, 0, BUFSIZE);
                    if(bufc == -1) {
                        bufc = 0;
                        return;
                    }
                }
            }

            private boolean pumpBuf() throws IOException {
                if(bufi == bufc) {
                    fillBuf();
                }
                return bufc != 0;
            }

            private boolean isDelimiter(char c) {
                return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f';
            }

            private void eatDelimiters() throws IOException {
                while(true) {
                    if(bufi == bufc) {
                        fillBuf();
                        if(bufc == 0) throw new RuntimeException("IO: Out of input.");
                    }

                    if(!isDelimiter(buf[bufi])) break;
                    ++bufi;
                }
            }

            public String next() {
                try {
                    sb.setLength(0);

                    eatDelimiters();
                    int start = bufi;

                    while(true) {
                        if(bufi == bufc) {
                            sb.append(buf, start, bufi - start);
                            fillBuf();
                            start = 0;
                            if(bufc == 0) break;
                        }

                        if(isDelimiter(buf[bufi])) break;
                        ++bufi;
                    }

                    sb.append(buf, start, bufi - start);

                    return sb.toString();
                } catch(IOException e) {
                    throw new RuntimeException("IO.next: Caught IOException.");
                }
            }

            public int nextInt() {
                try {
                    int ret = 0;

                    eatDelimiters();

                    boolean positive = true;
                    if(buf[bufi] == '-') {
                        ++bufi;
                        if(!pumpBuf()) throw new RuntimeException("IO.nextInt: Invalid int.");
                        positive = false;
                    }

                    boolean first = true;
                    while(true) {
                        if(!pumpBuf()) break;
                        if(isDelimiter(buf[bufi])) {
                            if(first) throw new RuntimeException("IO.nextInt: Invalid int.");
                            break;
                        }
                        first = false;

                        if(buf[bufi] >= '0' && buf[bufi] <= '9') {
                            if(ret < -214748364) throw new RuntimeException("IO.nextInt: Invalid int.");
                            ret *= 10;
                            ret -= (int)(buf[bufi] - '0');
                            if(ret > 0) throw new RuntimeException("IO.nextInt: Invalid int.");
                        } else {
                            throw new RuntimeException("IO.nextInt: Invalid int.");
                        }

                        ++bufi;
                    }

                    if(positive) {
                        if(ret == -2147483648) throw new RuntimeException("IO.nextInt: Invalid int.");
                        ret = -ret;
                    }

                    return ret;
                } catch(IOException e) {
                    throw new RuntimeException("IO.nextInt: Caught IOException.");
                }
            }

            public long nextLong() {
                try {
                    long ret = 0;

                    eatDelimiters();

                    boolean positive = true;
                    if(buf[bufi] == '-') {
                        ++bufi;
                        if(!pumpBuf()) throw new RuntimeException("IO.nextLong: Invalid long.");
                        positive = false;
                    }

                    boolean first = true;
                    while(true) {
                        if(!pumpBuf()) break;
                        if(isDelimiter(buf[bufi])) {
                            if(first) throw new RuntimeException("IO.nextLong: Invalid long.");
                            break;
                        }
                        first = false;

                        if(buf[bufi] >= '0' && buf[bufi] <= '9') {
                            if(ret < -922337203685477580L) throw new RuntimeException("IO.nextLong: Invalid long.");
                            ret *= 10;
                            ret -= (long)(buf[bufi] - '0');
                            if(ret > 0) throw new RuntimeException("IO.nextLong: Invalid long.");
                        } else {
                            throw new RuntimeException("IO.nextLong: Invalid long.");
                        }

                        ++bufi;
                    }

                    if(positive) {
                        if(ret == -9223372036854775808L) throw new RuntimeException("IO.nextLong: Invalid long.");
                        ret = -ret;
                    }

                    return ret;
                } catch(IOException e) {
                    throw new RuntimeException("IO.nextLong: Caught IOException.");
                }
            }

            public double nextDouble() {
                return Double.parseDouble(next());
            }

        }

        void print(Object output) {
            io.println(output);
        }

        void done(Object output) {
            print(output);
            done();
        }

        void done() {
            io.close();
            throw new RuntimeException("Clean exit");
        }

        long min(long... v) {
            long ans = v[0];
            for (int i=1; i<v.length; i++) {
                ans = Math.min(ans, v[i]);
            }
            return ans;
        }

        double min(double... v) {
            double ans = v[0];
            for (int i=1; i<v.length; i++) {
                ans = Math.min(ans, v[i]);
            }
            return ans;
        }

        int min(int... v) {
            int ans = v[0];
            for (int i=1; i<v.length; i++) {
                ans = Math.min(ans, v[i]);
            }
            return ans;
        }

        long max(long... v) {
            long ans = v[0];
            for (int i=1; i<v.length; i++) {
                ans = Math.max(ans, v[i]);
            }
            return ans;
        }

        double max(double... v) {
            double ans = v[0];
            for (int i=1; i<v.length; i++) {
                ans = Math.max(ans, v[i]);
            }
            return ans;
        }

        int max(int... v) {
            int ans = v[0];
            for (int i=1; i<v.length; i++) {
                ans = Math.max(ans, v[i]);
            }
            return ans;
        }

    }

}

//package baobab;

import java.io.*;
import java.util.*;

public class D {

    public static void main(String[] args) {
        Solver solver = new Solver();
    }

    static class Solver {
        IO io;

        public Solver() {
            this.io = new IO();
            try {
                solve();
            } catch (RuntimeException e) {
                if (!e.getMessage().equals("Clean exit")) {
                    throw e;
                }
            } finally {
                io.close();
            }
        }

        /****************************** START READING HERE ********************************/

        void solve() {
            int n = io.nextInt();
            int[][] b = new int[n][n];
            for (int y=0; y<n; y++) {
                for (int x=0; x<n; x++) {
                    b[y][x] = io.nextInt();
                }
            }
            int end = n*n;
            int[] x = new int[end+1];
            int[] y = new int[end+1];
            for (int i=0; i<n; i++) {
                for (int j=0; j<n; j++) {
                    int v = b[i][j];
                    y[v] = i;
                    x[v] = j;
                }
            }

            State ans = null;
            State[][][][][] leastSteps = new State[n][n][n][n][3];
            Deque<State> q = new ArrayDeque<>();
            q.add(new State(y[1], x[1], y[2], x[2], ROOK, 0, 0));
            q.add(new State(y[1], x[1], y[2], x[2], KNIGHT, 0, 0));
            q.add(new State(y[1], x[1], y[2], x[2], BISHOP, 0, 0));
            while (!q.isEmpty()) {
                State s = q.pollFirst();
                if (s.currY < 0 || s.currX < 0 || s.currY >= n || s.currX >= n) continue;
                State prev = leastSteps[s.currY][s.currX][s.goalY][s.goalX][s.piece];
                if (prev != null) {
                    if (prev.steps < s.steps) continue;
                    if (prev.steps == s.steps && prev.replacements <= s.replacements) continue;
                }
                leastSteps[s.currY][s.currX][s.goalY][s.goalX][s.piece] = s;

                // intermediary goal reached?
                if (s.currX == s.goalX && s.currY == s.goalY) {

                    if (b[s.currY][s.currX] == end) {
                        // actually reached end state with fewest steps but not necessarily fewest replacements for that step size
                        if (ans == null) ans = s;
                        else if (ans.replacements >= s.replacements) ans = s;
                        continue; // stop bfs but dont break because we may find new solution with same steps but fewer replacements
                    }

                    // change state to set next goal.
                    int prevGoal = b[s.goalY][s.goalX];
                    int newGoal = prevGoal + 1;
                    int newGoalX = x[newGoal];
                    int newGoalY = y[newGoal];
                    // front of the queue!
                    q.addFirst(new State(s.goalY, s.goalX, newGoalY, newGoalX, s.piece, s.steps, s.replacements));
                    continue;
                }

                // change piece?
                q.addLast(new State(s.currY, s.currX, s.goalY, s.goalX, ROOK, s.steps+1, s.replacements+1));
                q.addLast(new State(s.currY, s.currX, s.goalY, s.goalX, BISHOP, s.steps+1, s.replacements+1));
                q.addLast(new State(s.currY, s.currX, s.goalY, s.goalX, KNIGHT, s.steps+1, s.replacements+1));

                // move current piece?
                if (s.piece == ROOK) {
                    for (int newY=0; newY<n; newY++) q.addLast(new State(newY, s.currX, s.goalY, s.goalX, s.piece, s.steps+1, s.replacements));
                    for (int newX=0; newX<n; newX++) q.addLast(new State(s.currY, newX, s.goalY, s.goalX, s.piece, s.steps+1, s.replacements));
                } else if (s.piece == KNIGHT) {
                    int newY = s.currY + 1;
                    int newX = s.currX + 2;
                    q.addLast(new State(newY, newX, s.goalY, s.goalX, s.piece, s.steps+1, s.replacements));
                    newY = s.currY + 1;
                    newX = s.currX - 2;
                    q.addLast(new State(newY, newX, s.goalY, s.goalX, s.piece, s.steps+1, s.replacements));
                    newY = s.currY - 1;
                    newX = s.currX - 2;
                    q.addLast(new State(newY, newX, s.goalY, s.goalX, s.piece, s.steps+1, s.replacements));
                    newY = s.currY - 1;
                    newX = s.currX + 2;
                    q.addLast(new State(newY, newX, s.goalY, s.goalX, s.piece, s.steps+1, s.replacements));
                    newY = s.currY + 2;
                    newX = s.currX + 1;
                    q.addLast(new State(newY, newX, s.goalY, s.goalX, s.piece, s.steps+1, s.replacements));
                    newY = s.currY - 2;
                    newX = s.currX + 1;
                    q.addLast(new State(newY, newX, s.goalY, s.goalX, s.piece, s.steps+1, s.replacements));
                    newY = s.currY + 2;
                    newX = s.currX - 1;
                    q.addLast(new State(newY, newX, s.goalY, s.goalX, s.piece, s.steps+1, s.replacements));
                    newY = s.currY - 2;
                    newX = s.currX - 1;
                    q.addLast(new State(newY, newX, s.goalY, s.goalX, s.piece, s.steps+1, s.replacements));

                } else if (s.piece == BISHOP) {
                    int newY = s.currY;
                    int newX = s.currX;
                    for (int diff=1 ;; diff++) {
                        newY += diff;
                        newX += diff;
                        if (newX >= n || newX < 0 || newY >= n || newY < 0) break;
                        q.addLast(new State(newY, newX, s.goalY, s.goalX, s.piece, s.steps+1, s.replacements));
                    }
                    newY = s.currY;
                    newX = s.currX;
                    for (int diff=1 ;; diff++) {
                        newY += diff;
                        newX -= diff;
                        if (newX >= n || newX < 0 || newY >= n || newY < 0) break;
                        q.addLast(new State(newY, newX, s.goalY, s.goalX, s.piece, s.steps+1, s.replacements));
                    }
                    newY = s.currY;
                    newX = s.currX;
                    for (int diff=1 ;; diff++) {
                        newY -= diff;
                        newX -= diff;
                        if (newX >= n || newX < 0 || newY >= n || newY < 0) break;
                        q.addLast(new State(newY, newX, s.goalY, s.goalX, s.piece, s.steps+1, s.replacements));
                    }
                    newY = s.currY;
                    newX = s.currX;
                    for (int diff=1 ;; diff++) {
                        newY -= diff;
                        newX += diff;
                        if (newX >= n || newX < 0 || newY >= n || newY < 0) break;
                        q.addLast(new State(newY, newX, s.goalY, s.goalX, s.piece, s.steps+1, s.replacements));
                    }
                }
            }

            io.println(ans.steps + " " + ans.replacements);
        }

        static int ROOK = 0;
        static int KNIGHT = 1;
        static int BISHOP = 2;

        class State {
            int currY;
            int currX;
            int goalY;
            int goalX;
            int piece;
            int steps;
            int replacements;

            public State(int currY, int currX, int goalY, int goalX, int piece, int steps, int replacements) {
                this.currY = currY;
                this.currX = currX;
                this.goalY = goalY;
                this.goalX = goalX;
                this.piece = piece;
                this.steps = steps;
                this.replacements = replacements;
            }

            @Override
            public boolean equals(Object o) {
                if (this == o) return true;
                if (o == null || getClass() != o.getClass()) return false;
                State state = (State) o;
                return currY == state.currY &&
                        currX == state.currX &&
                        goalY == state.goalY &&
                        goalX == state.goalX &&
                        piece == state.piece;
            }

            @Override
            public int hashCode() {
                return Objects.hash(currY, currX, goalY, goalX, piece);
            }
        }

        /************************** UTILITY CODE BELOW THIS LINE **************************/

        long MOD = (long)1e9 + 7;

        boolean closeToZero(double v) {
            // Check if double is close to zero, considering precision issues.
            return Math.abs(v) <= 0.0000000001;
        }

        class DrawGrid {

            void draw(boolean[][] d) {
                System.out.print("  ");
                for (int x=0; x<d[0].length; x++) {
                    System.out.print(" " + x + " ");
                }
                System.out.println("");
                for (int y=0; y<d.length; y++) {
                    System.out.print(y + " ");
                    for (int x=0; x<d[0].length; x++) {
                        System.out.print((d[y][x] ? "[x]" : "[ ]"));
                    }
                    System.out.println("");
                }
            }

            void draw(int[][] d) {
                int max = 1;
                for (int y=0; y<d.length; y++) {
                    for (int x=0; x<d[0].length; x++) {
                        max = Math.max(max, ("" + d[y][x]).length());
                    }
                }
                System.out.print("  ");
                String format = "%" + (max+2) + "s";
                for (int x=0; x<d[0].length; x++) {
                    System.out.print(String.format(format, x) + " ");
                }
                format = "%" + (max) + "s";
                System.out.println("");
                for (int y=0; y<d.length; y++) {
                    System.out.print(y + " ");
                    for (int x=0; x<d[0].length; x++) {
                        System.out.print(" [" + String.format(format, (d[y][x])) + "]");
                    }
                    System.out.println("");
                }
            }

        }

        class IDval implements Comparable<IDval> {
            int steps;
            long replacements;

            public IDval(int steps, long replacements) {
                this.replacements = replacements;
                this.steps = steps;
            }

            @Override
            public int compareTo(IDval o) {
                if (this.replacements < o.replacements) return -1;
                if (this.replacements > o.replacements) return 1;
                return this.steps - o.steps;
            }
        }

        private class ElementCounter {
            private HashMap<Long, Integer> elements;

            public ElementCounter() {
                elements = new HashMap<>();
            }

            public void add(long element) {
                int count = 1;
                Integer prev = elements.get(element);
                if (prev != null) count += prev;
                elements.put(element, count);
            }

            public void remove(long element) {
                int count = elements.remove(element);
                count--;
                if (count > 0) elements.put(element, count);
            }

            public int get(long element) {
                Integer val = elements.get(element);
                if (val == null) return 0;
                return val;
            }

            public int size() {
                return elements.size();
            }
        }

        class StringCounter {
            HashMap<String, Integer> elements;

            public StringCounter() {
                elements = new HashMap<>();
            }

            public void add(String identifier) {
                int count = 1;
                Integer prev = elements.get(identifier);
                if (prev != null) count += prev;
                elements.put(identifier, count);
            }

            public void remove(String identifier) {
                int count = elements.remove(identifier);
                count--;
                if (count > 0) elements.put(identifier, count);
            }

            public long get(String identifier) {
                Integer val = elements.get(identifier);
                if (val == null) return 0;
                return val;
            }

            public int size() {
                return elements.size();
            }
        }

        class DisjointSet {
            /** Union Find / Disjoint Set data structure. */
            int[] size;
            int[] parent;
            int componentCount;

            public DisjointSet(int n) {
                componentCount = n;
                size = new int[n];
                parent = new int[n];
                for (int i=0; i<n; i++) parent[i] = i;
                for (int i=0; i<n; i++) size[i] = 1;
            }

            public void join(int a, int b) {
                /* Find roots */
                int rootA = parent[a];
                int rootB = parent[b];
                while (rootA != parent[rootA]) rootA = parent[rootA];
                while (rootB != parent[rootB]) rootB = parent[rootB];

                if (rootA == rootB) {
                    /* Already in the same set */
                    return;
                }

                /* Merge smaller set into larger set. */
                if (size[rootA] > size[rootB]) {
                    size[rootA] += size[rootB];
                    parent[rootB] = rootA;
                } else {
                    size[rootB] += size[rootA];
                    parent[rootA] = rootB;
                }
                componentCount--;
            }

        }

        class Trie {
            int N;
            int Z;
            int nextFreeId;
            int[][] pointers;
            boolean[] end;

            /** maxLenSum = maximum possible sum of length of words */
            public Trie(int maxLenSum, int alphabetSize) {
                this.N = maxLenSum;
                this.Z = alphabetSize;
                this.nextFreeId = 1;
                pointers = new int[N+1][alphabetSize];
                end = new boolean[N+1];
            }

            public void addWord(String word) {
                int curr = 0;
                for (int j=0; j<word.length(); j++) {
                    int c = word.charAt(j) - 'a';
                    int next = pointers[curr][c];
                    if (next == 0) {
                        next = nextFreeId++;
                        pointers[curr][c] = next;
                    }
                    curr = next;
                }
                end[curr] = true;
            }

            public boolean hasWord(String word) {
                int curr = 0;
                for (int j=0; j<word.length(); j++) {
                    int c = word.charAt(j) - 'a';
                    int next = pointers[curr][c];
                    if (next == 0) return false;
                    curr = next;
                }
                return end[curr];
            }

        }

        private static class Prob {

            /** For heavy calculations on probabilities, this class
             *  provides more accuracy & efficiency than doubles.
             *  Math explained: https://en.wikipedia.org/wiki/Log_probability
             *  Quick start:
             *      - Instantiate probabilities, eg. Prob a = new Prob(0.75)
             *      - add(), multiply() return new objects, can perform on nulls & NaNs.
             *      - get() returns probability as a readable double */

            /** Logarithmized probability. Note: 0% represented by logP NaN. */
            private double logP;

            /** Construct instance with real probability. */
            public Prob(double real) {
                if (real > 0) this.logP = Math.log(real);
                else this.logP = Double.NaN;
            }

            /** Construct instance with already logarithmized value. */
            static boolean dontLogAgain = true;
            public Prob(double logP, boolean anyBooleanHereToChooseThisConstructor) {
                this.logP = logP;
            }

            /** Returns real probability as a double. */
            public double get() {
                return Math.exp(logP);
            }

            @Override
            public String toString() {
                return ""+get();
            }

            /***************** STATIC METHODS BELOW ********************/

            /** Note: returns NaN only when a && b are both NaN/null. */
            public static Prob add(Prob a, Prob b) {
                if (nullOrNaN(a) && nullOrNaN(b)) return new Prob(Double.NaN, dontLogAgain);
                if (nullOrNaN(a)) return copy(b);
                if (nullOrNaN(b)) return copy(a);

                double x = Math.max(a.logP, b.logP);
                double y = Math.min(a.logP, b.logP);
                double sum = x + Math.log(1 + Math.exp(y - x));
                return new Prob(sum, dontLogAgain);
            }

            /** Note: multiplying by null or NaN produces NaN (repping 0% real prob). */
            public static Prob multiply(Prob a, Prob b) {
                if (nullOrNaN(a) || nullOrNaN(b)) return new Prob(Double.NaN, dontLogAgain);
                return new Prob(a.logP + b.logP, dontLogAgain);
            }

            /** Returns true if p is null or NaN. */
            private static boolean nullOrNaN(Prob p) {
                return (p == null || Double.isNaN(p.logP));
            }

            /** Returns a new instance with the same value as original. */
            private static Prob copy(Prob original) {
                return new Prob(original.logP, dontLogAgain);
            }
        }

        class Binary implements Comparable<Binary> {

            /**
             * Use example: Binary b = new Binary(Long.toBinaryString(53249834L));
             *
             * When manipulating small binary strings, instantiate new Binary(string)
             * When just reading large binary strings, instantiate new Binary(string,true)
             * get(int i) returns a character '1' or '0', not an int.
             */

            private boolean[] d;
            private int first; // Starting from left, the first (most remarkable) '1'
            public int length;

            public Binary(String binaryString) {
                this(binaryString, false);
            }
            public Binary(String binaryString, boolean initWithMinArraySize) {
                length = binaryString.length();
                int size = Math.max(2*length, 1);
                first = length/4;
                if (initWithMinArraySize) {
                    first = 0;
                    size = Math.max(length, 1);
                }
                d = new boolean[size];
                for (int i=0; i<length; i++) {
                    if (binaryString.charAt(i) == '1') d[i+first] = true;
                }
            }

            public void addFirst(char c) {
                if (first-1 < 0) doubleArraySize();
                first--;
                d[first] = (c == '1' ? true : false);
                length++;
            }

            public void addLast(char c) {
                if (first+length >= d.length) doubleArraySize();
                d[first+length] = (c == '1' ? true : false);
                length++;
            }

            private void doubleArraySize() {
                boolean[] bigArray = new boolean[(d.length+1) * 2];
                int newFirst = bigArray.length / 4;
                for (int i=0; i<length; i++) {
                    bigArray[i + newFirst] = d[i + first];
                }
                first = newFirst;
                d = bigArray;
            }

            public boolean flip(int i) {
                boolean value = (this.d[first+i] ? false : true);
                this.d[first+i] = value;
                return value;
            }

            public void set(int i, char c) {
                boolean value = (c == '1' ? true : false);
                this.d[first+i] = value;
            }

            public char get(int i) {
                return (this.d[first+i] ? '1' : '0');
            }

            @Override
            public int compareTo(Binary o) {
                if (this.length != o.length) return this.length - o.length;
                int len = this.length;
                for (int i=0; i<len; i++) {
                    int diff = this.get(i) - o.get(i);
                    if (diff != 0) return diff;
                }
                return 0;
            }

            @Override
            public String toString() {
                StringBuilder sb = new StringBuilder();
                for (int i=0; i<length; i++) {
                    sb.append(d[i+first] ? '1' : '0');
                }
                return sb.toString();
            }

        }

        /************************** Range queries **************************/

        class FenwickMin {
            long n;
            long[] original;
            long[] bottomUp;
            long[] topDown;

            public FenwickMin(int n) {
                this.n = n;
                original = new long[n+2];
                bottomUp = new long[n+2];
                topDown = new long[n+2];
            }

            public void set(int modifiedNode, long value) {
                long replaced = original[modifiedNode];
                original[modifiedNode] = value;
                // Update left tree
                int i = modifiedNode;
                long v = value;
                while (i <= n) {
                    if (v > bottomUp[i]) {
                        if (replaced == bottomUp[i]) {
                            v = Math.min(v, original[i]);
                            for (int r=1 ;; r++) {
                                int x = (i&-i)>>>r;
                                if (x == 0) break;
                                int child = i-x;
                                v = Math.min(v, bottomUp[child]);
                            }
                        } else break;
                    }
                    if (v == bottomUp[i]) break;
                    bottomUp[i] = v;
                    i += (i&-i);
                }
                // Update right tree
                i = modifiedNode;
                v = value;
                while (i > 0) {
                    if (v > topDown[i]) {
                        if (replaced == topDown[i]) {
                            v = Math.min(v, original[i]);
                            for (int r=1 ;; r++) {
                                int x = (i&-i)>>>r;
                                if (x == 0) break;
                                int child = i+x;
                                if (child > n+1) break;
                                v = Math.min(v, topDown[child]);
                            }
                        } else break;
                    }
                    if (v == topDown[i]) break;
                    topDown[i] = v;
                    i -= (i&-i);
                }
            }

            public long getMin(int a, int b) {
                long min = original[a];
                int prev = a;
                int curr = prev + (prev&-prev); // parent right hand side
                while (curr <= b) {
                    min = Math.min(min, topDown[prev]); // value from the other tree
                    prev = curr;
                    curr = prev + (prev&-prev);;
                }
                min = Math.min(min, original[prev]);
                prev = b;
                curr = prev - (prev&-prev); // parent left hand side
                while (curr >= a) {
                    min = Math.min(min,bottomUp[prev]); // value from the other tree
                    prev = curr;
                    curr = prev - (prev&-prev);
                }
                return min;
            }

        }

        class FenwickSum {
            public long[] d;

            public FenwickSum(int n) {
                d=new long[n+1];
            }

            /** a[0] must be unused. */
            public FenwickSum(long[] a) {
                d=new long[a.length];
                for (int i=1; i<a.length; i++) {
                    modify(i, a[i]);
                }
            }

            /** Do not modify i=0. */
            void modify(int i, long v) {
                while (i<d.length) {
                    d[i] += v;
                    // Move to next uplink on the RIGHT side of i
                    i += (i&-i);
                }
            }

            /** Returns sum from a to b, *BOTH* inclusive. */
            long getSum(int a, int b) {
                return getSum(b) - getSum(a-1);
            }

            private long getSum(int i) {
                long sum = 0;
                while (i>0) {
                    sum += d[i];
                    // Move to next uplink on the LEFT side of i
                    i -= (i&-i);
                }
                return sum;
            }
        }

        class SegmentTree {

            /* Provides log(n) operations for:
             * - Range query (sum, min or max)
             * - Range update ("+8 to all values between indexes 4 and 94")
             */

            int N;
            long[] lazy;
            long[] sum;
            long[] min;
            long[] max;
            boolean supportSum;
            boolean supportMin;
            boolean supportMax;

            public SegmentTree(int n) {
                this(n, true, true, true);
            }

            public SegmentTree(int n, boolean supportSum, boolean supportMin, boolean supportMax) {
                for (N=2; N<n;) N*=2;
                this.lazy = new long[2*N];
                this.supportSum = supportSum;
                this.supportMin = supportMin;
                this.supportMax = supportMax;
                if (this.supportSum) this.sum = new long[2*N];
                if (this.supportMin) this.min = new long[2*N];
                if (this.supportMax) this.max = new long[2*N];
            }

            void modifyRange(long x, int a, int b) {
                modifyRec(a, b, 1, 0, N-1, x);
            }

            void setRange() {
                //TODO
            }

            long getSum(int a, int b) {
                return querySum(a, b, 1, 0, N-1);
            }

            long getMin(int a, int b) {
                return queryMin(a, b, 1, 0, N-1);
            }

            long getMax(int a, int b) {
                return queryMax(a, b, 1, 0, N-1);
            }

            private long querySum(int wantedLeft, int wantedRight, int i, int actualLeft, int actualRight) {
                if (wantedLeft > actualRight || wantedRight < actualLeft) {
                    return 0;
                }
                if (wantedLeft == actualLeft && wantedRight == actualRight) {
                    int count = wantedRight - wantedLeft + 1;
                    return sum[i] + count * lazy[i];
                }
                if (lazy[i] != 0) propagate(i, actualLeft, actualRight);
                int d = (actualRight - actualLeft + 1) / 2;
                long left = querySum(wantedLeft, min(actualLeft+d-1, wantedRight), 2*i, actualLeft, actualLeft+d-1);
                long right = querySum(max(actualLeft+d, wantedLeft), wantedRight, 2*i+1, actualLeft+d, actualRight);
                return left + right;
            }

            private long queryMin(int wantedLeft, int wantedRight, int i, int actualLeft, int actualRight) {
                if (wantedLeft > actualRight || wantedRight < actualLeft) {
                    return 0;
                }
                if (wantedLeft == actualLeft && wantedRight == actualRight) {
                    return min[i] + lazy[i];
                }
                if (lazy[i] != 0) propagate(i, actualLeft, actualRight);
                int d = (actualRight - actualLeft + 1) / 2;
                long left = queryMin(wantedLeft, min(actualLeft+d-1, wantedRight), 2*i, actualLeft, actualLeft+d-1);
                long right = queryMin(max(actualLeft+d, wantedLeft), wantedRight, 2*i+1, actualLeft+d, actualRight);
                return min(left, right);
            }

            private long queryMax(int wantedLeft, int wantedRight, int i, int actualLeft, int actualRight) {
                if (wantedLeft > actualRight || wantedRight < actualLeft) {
                    return 0;
                }
                if (wantedLeft == actualLeft && wantedRight == actualRight) {
                    return max[i] + lazy[i];
                }
                if (lazy[i] != 0) propagate(i, actualLeft, actualRight);
                int d = (actualRight - actualLeft + 1) / 2;
                long left = queryMax(wantedLeft, min(actualLeft+d-1, wantedRight), 2*i, actualLeft, actualLeft+d-1);
                long right = queryMax(max(actualLeft+d, wantedLeft), wantedRight, 2*i+1, actualLeft+d, actualRight);
                return max(left, right);
            }

            private void modifyRec(int wantedLeft, int wantedRight, int i, int actualLeft, int actualRight, long value) {
                if (wantedLeft > actualRight || wantedRight < actualLeft) {
                    return;
                }
                if (wantedLeft == actualLeft && wantedRight == actualRight) {
                    lazy[i] += value;
                    return;
                }
                if (lazy[i] != 0) propagate(i, actualLeft, actualRight);
                int d = (actualRight - actualLeft + 1) / 2;
                modifyRec(wantedLeft, min(actualLeft+d-1, wantedRight), 2*i, actualLeft, actualLeft+d-1, value);
                modifyRec(max(actualLeft+d, wantedLeft), wantedRight, 2*i+1, actualLeft+d, actualRight, value);
                if (supportSum) sum[i] += value * (min(actualRight, wantedRight) - max(actualLeft, wantedLeft) + 1);
                if (supportMin) min[i] = min(min[2*i] + lazy[2*i], min[2*i+1] + lazy[2*i+1]);
                if (supportMax) max[i] = max(max[2*i] + lazy[2*i], max[2*i+1] + lazy[2*i+1]);
            }

            private void propagate(int i, int actualLeft, int actualRight) {
                lazy[2*i] += lazy[i];
                lazy[2*i+1] += lazy[i];
                if (supportSum) sum[i] += lazy[i] * (actualRight - actualLeft + 1);
                if (supportMin) min[i] += lazy[i];
                if (supportMax) max[i] += lazy[i];
                lazy[i] = 0;
            }

        }

        /***************************** Graphs *****************************/

        List<Integer>[] toGraph(IO io, int n) {
            List<Integer>[] g = new ArrayList[n+1];
            for (int i=1; i<=n; i++) g[i] = new ArrayList<>();
            for (int i=1; i<=n-1; i++) {
                int a = io.nextInt();
                int b = io.nextInt();
                g[a].add(b);
                g[b].add(a);
            }
            return g;
        }

        class Graph {
            HashMap<Long, List<Long>> edges;

            public Graph() {
                edges = new HashMap<>();
            }

            List<Long> getSetNeighbors(Long node) {
                List<Long> neighbors = edges.get(node);
                if (neighbors == null) {
                    neighbors = new ArrayList<>();
                    edges.put(node, neighbors);
                }
                return neighbors;
            }

            void addBiEdge(Long a, Long b) {
                addEdge(a, b);
                addEdge(b, a);
            }

            void addEdge(Long from, Long to) {
                getSetNeighbors(to); // make sure all have initialized lists
                List<Long> neighbors = getSetNeighbors(from);
                neighbors.add(to);
            }

            // topoSort variables
            int UNTOUCHED = 0;
            int FINISHED = 2;
            int INPROGRESS = 1;
            HashMap<Long, Integer> vis;
            List<Long> topoAns;
            List<Long> failDueToCycle = new ArrayList<Long>() {{ add(-1L); }};

            List<Long> topoSort() {
                topoAns = new ArrayList<>();
                vis = new HashMap<>();
                for (Long a : edges.keySet()) {
                    if (!topoDFS(a)) return failDueToCycle;
                }
                Collections.reverse(topoAns);
                return topoAns;
            }

            boolean topoDFS(long curr) {
                Integer status = vis.get(curr);
                if (status == null) status = UNTOUCHED;
                if (status == FINISHED) return true;
                if (status == INPROGRESS) {
                    return false;
                }
                vis.put(curr, INPROGRESS);
                for (long next : edges.get(curr)) {
                    if (!topoDFS(next)) return false;
                }
                vis.put(curr, FINISHED);
                topoAns.add(curr);
                return true;
            }

        }

        public class StronglyConnectedComponents {

            /** Kosaraju's algorithm */

            ArrayList<Integer>[] forw;
            ArrayList<Integer>[] bacw;

            /** Use: getCount(2, new int[] {1,2}, new int[] {2,1}) */
            public int getCount(int n, int[] mista, int[] minne) {
                forw = new ArrayList[n+1];
                bacw = new ArrayList[n+1];
                for (int i=1; i<=n; i++) {
                    forw[i] = new ArrayList<Integer>();
                    bacw[i] = new ArrayList<Integer>();
                }
                for (int i=0; i<mista.length; i++) {
                    int a = mista[i];
                    int b = minne[i];
                    forw[a].add(b);
                    bacw[b].add(a);
                }
                int count = 0;
                List<Integer> list = new ArrayList<Integer>();
                boolean[] visited = new boolean[n+1];
                for (int i=1; i<=n; i++) {
                    dfsForward(i, visited, list);
                }
                visited = new boolean[n+1];
                for (int i=n-1; i>=0; i--) {
                    int node = list.get(i);
                    if (visited[node]) continue;
                    count++;
                    dfsBackward(node, visited);
                }
                return count;
            }

            public void dfsForward(int i, boolean[] visited, List<Integer> list) {
                if (visited[i]) return;
                visited[i] = true;
                for (int neighbor : forw[i]) {
                    dfsForward(neighbor, visited, list);
                }
                list.add(i);
            }

            public void dfsBackward(int i, boolean[] visited) {
                if (visited[i]) return;
                visited[i] = true;
                for (int neighbor : bacw[i]) {
                    dfsBackward(neighbor, visited);
                }
            }
        }

        class LCAFinder {

            /* O(n log n) Initialize: new LCAFinder(graph)
             * O(log n) Queries: find(a,b) returns lowest common ancestor for nodes a and b */

            int[] nodes;
            int[] depths;
            int[] entries;
            int pointer;
            FenwickMin fenwick;

            public LCAFinder(List<Integer>[] graph) {
                this.nodes = new int[(int)10e6];
                this.depths = new int[(int)10e6];
                this.entries = new int[graph.length];
                this.pointer = 1;
                boolean[] visited = new boolean[graph.length+1];
                dfs(1, 0, graph, visited);
                fenwick = new FenwickMin(pointer-1);
                for (int i=1; i<pointer; i++) {
                    fenwick.set(i, depths[i] * 1000000L + i);
                }
            }

            private void dfs(int node, int depth, List<Integer>[] graph, boolean[] visited) {
                visited[node] = true;
                entries[node] = pointer;
                nodes[pointer] = node;
                depths[pointer] = depth;
                pointer++;
                for (int neighbor : graph[node]) {
                    if (visited[neighbor]) continue;
                    dfs(neighbor, depth+1, graph, visited);
                    nodes[pointer] = node;
                    depths[pointer] = depth;
                    pointer++;
                }
            }

            public int find(int a, int b) {
                int left = entries[a];
                int right = entries[b];
                if (left > right) {
                    int temp = left;
                    left = right;
                    right = temp;
                }
                long mixedBag = fenwick.getMin(left, right);
                int index = (int) (mixedBag % 1000000L);
                return nodes[index];
            }
        }

        /**************************** Geometry ****************************/

        class Point {
            int y;
            int x;

            public Point(int y, int x) {
                this.y = y;
                this.x = x;
            }
        }

        boolean segmentsIntersect(double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4) {
            // Returns true if segment 1-2 intersects segment 3-4

            if (x1 == x2 && x3 == x4) {
                // Both segments are vertical
                if (x1 != x3) return false;
                if (min(y1,y2) < min(y3,y4)) {
                    return max(y1,y2) >= min(y3,y4);
                } else {
                    return max(y3,y4) >= min(y1,y2);
                }
            }
            if (x1 == x2) {
                // Only segment 1-2 is vertical. Does segment 3-4 cross it? y = a*x + b
                double a34 = (y4-y3)/(x4-x3);
                double b34 = y3 - a34*x3;
                double y = a34 * x1 + b34;
                return y >= min(y1,y2) && y <= max(y1,y2) && x1 >= min(x3,x4) && x1 <= max(x3,x4);
            }
            if (x3 == x4) {
                // Only segment 3-4 is vertical. Does segment 1-2 cross it? y = a*x + b
                double a12 = (y2-y1)/(x2-x1);
                double b12 = y1 - a12*x1;
                double y = a12 * x3 + b12;
                return y >= min(y3,y4) && y <= max(y3,y4) && x3 >= min(x1,x2) && x3 <= max(x1,x2);
            }
            double a12 = (y2-y1)/(x2-x1);
            double b12 = y1 - a12*x1;
            double a34 = (y4-y3)/(x4-x3);
            double b34 = y3 - a34*x3;
            if (closeToZero(a12 - a34)) {
                // Parallel lines
                return closeToZero(b12 - b34);
            }
            // Non parallel non vertical lines intersect at x. Is x part of both segments?
            double x = -(b12-b34)/(a12-a34);
            return x >= min(x1,x2) && x <= max(x1,x2) && x >= min(x3,x4) && x <= max(x3,x4);
        }

        boolean pointInsideRectangle(Point p, List<Point> r, boolean countBorderAsInside) {
            Point a = r.get(0);
            Point b = r.get(1);
            Point c = r.get(2);
            Point d = r.get(3);
            double apd = areaOfTriangle(a, p, d);
            double dpc = areaOfTriangle(d, p, c);
            double cpb = areaOfTriangle(c, p, b);
            double pba = areaOfTriangle(p, b, a);
            double sumOfAreas = apd + dpc + cpb + pba;
            if (closeToZero(sumOfAreas - areaOfRectangle(r))) {
                if (closeToZero(apd) || closeToZero(dpc) || closeToZero(cpb) || closeToZero(pba)) {
                    return countBorderAsInside;
                }
                return true;
            }
            return false;
        }

        double areaOfTriangle(Point a, Point b, Point c) {
            return 0.5 * Math.abs((a.x-c.x)*(b.y-a.y)-(a.x-b.x)*(c.y-a.y));
        }

        double areaOfRectangle(List<Point> r) {
            double side1xDiff = r.get(0).x - r.get(1).x;
            double side1yDiff = r.get(0).y - r.get(1).y;
            double side2xDiff = r.get(1).x - r.get(2).x;
            double side2yDiff = r.get(1).y - r.get(2).y;
            double side1 = Math.sqrt(side1xDiff * side1xDiff + side1yDiff * side1yDiff);
            double side2 = Math.sqrt(side2xDiff * side2xDiff + side2yDiff * side2yDiff);
            return side1 * side2;
        }

        boolean pointsOnSameLine(double x1, double y1, double x2, double y2, double x3, double y3) {
            double areaTimes2 = x1 * (y2 - y3) + x2 * (y3 - y1) + x3 * (y1 - y2);
            return (closeToZero(areaTimes2));
        }

        class PointToLineSegmentDistanceCalculator {

            // Just call this
            double minDistFromPointToLineSegment(double point_x, double point_y, double x1, double y1, double x2, double y2) {
                return Math.sqrt(distToSegmentSquared(point_x, point_y, x1, y1, x2, y2));
            }

            private double distToSegmentSquared(double point_x, double point_y, double x1, double y1, double x2, double y2) {
                double l2 = dist2(x1,y1,x2,y2);
                if (l2 == 0) return dist2(point_x, point_y, x1, y1);
                double t = ((point_x - x1) * (x2 - x1) + (point_y - y1) * (y2 - y1)) / l2;
                if (t < 0) return dist2(point_x, point_y, x1, y1);
                if (t > 1) return dist2(point_x, point_y, x2, y2);

                double com_x = x1 + t * (x2 - x1);
                double com_y = y1 + t * (y2 - y1);
                return dist2(point_x, point_y, com_x, com_y);
            }

            private double dist2(double x1, double y1, double x2, double y2) {
                return Math.pow((x1 - x2), 2) + Math.pow((y1 - y2), 2);
            }

        }

        /****************************** Math ******************************/

        long pow(long base, int exp) {
            if (exp == 0) return 1L;
            long x = pow(base, exp/2);
            long ans = x * x;
            if (exp % 2 != 0) ans *= base;
            return ans;
        }

        long gcd(long... v) {
            /** Chained calls to Euclidean algorithm. */
            if (v.length == 1) return v[0];
            long ans = gcd(v[1], v[0]);
            for (int i=2; i<v.length; i++) {
                ans = gcd(ans, v[i]);
            }
            return ans;
        }

        long gcd(long a, long b) {
            /** Euclidean algorithm. */
            if (b == 0) return a;
            return gcd(b, a%b);
        }

        int[] generatePrimesUpTo(int last) {
            /* Sieve of Eratosthenes. Practically O(n). Values of 0 indicate primes. */
            int[] div = new int[last+1];
            for (int x=2; x<=last; x++) {
                if (div[x] > 0) continue;
                for (int u=2*x; u<=last; u+=x) {
                    div[u] = x;
                }
            }
            return div;
        }

        long lcm(long a, long b) {
            /** Least common multiple */
            return a * b / gcd(a,b);
        }

        class BaseConverter {

            /* Palauttaa luvun esityksen kannassa base */
            public String convert(Long number, int base) {
                return Long.toString(number, base);
            }

            /* Palauttaa luvun esityksen kannassa baseTo, kun annetaan luku Stringinä kannassa baseFrom */
            public String convert(String number, int baseFrom, int baseTo) {
                return Long.toString(Long.parseLong(number, baseFrom), baseTo);
            }

            /* Tulkitsee kannassa base esitetyn luvun longiksi (kannassa 10) */
            public long longify(String number, int baseFrom) {
                return Long.parseLong(number, baseFrom);
            }
        }

        class BinomialCoefficients {
            /** Total number of K sized unique combinations from pool of size N (unordered)
             N! / ( K! (N - K)! )   */

            /** For simple queries where output fits in long. */
            public long biCo(long n, long k) {
                long r = 1;
                if (k > n) return 0;
                for (long d = 1; d <= k; d++) {
                    r *= n--;
                    r /= d;
                }
                return r;
            }

            /** For multiple queries with same n, different k. */
            public long[] precalcBinomialCoefficientsK(int n, int maxK) {
                long v[] = new long[maxK+1];
                v[0] = 1; // nC0 == 1
                for (int i=1; i<=n; i++) {
                    for (int j=Math.min(i,maxK); j>0; j--) {
                        v[j] = v[j] + v[j-1]; // Pascal's triangle
                    }
                }
                return v;
            }

            /** When output needs % MOD. */
            public long[] precalcBinomialCoefficientsK(int n, int k, long M) {
                long v[] = new long[k+1];
                v[0] = 1; // nC0 == 1
                for (int i=1; i<=n; i++) {
                    for (int j=Math.min(i,k); j>0; j--) {
                        v[j] = v[j] + v[j-1]; // Pascal's triangle
                        v[j] %= M;
                    }
                }
                return v;
            }
        }

        /**************************** Strings ****************************/

        class Zalgo {

            public int pisinEsiintyma(String haku, String kohde) {
                char[] s = new char[haku.length() + 1 + kohde.length()];
                for (int i=0; i<haku.length(); i++) {
                    s[i] = haku.charAt(i);
                }
                int j = haku.length();
                s[j++] = '#';
                for (int i=0; i<kohde.length(); i++) {
                    s[j++] = kohde.charAt(i);
                }
                int[] z = toZarray(s);
                int max = 0;
                for (int i=haku.length(); i<z.length; i++) {
                    max = Math.max(max, z[i]);
                }
                return max;
            }

            public int[] toZarray(char[] s) {
                int n = s.length;
                int[] z = new int[n];
                int a = 0, b = 0;
                for (int i = 1; i < n; i++) {
                    if (i > b) {
                        for (int j = i; j < n && s[j - i] == s[j]; j++)  z[i]++;
                    }
                    else {
                        z[i] = z[i - a];
                        if (i + z[i - a] > b) {
                            for (int j = b + 1; j < n && s[j - i] == s[j]; j++) z[i]++;
                            a = i;
                            b = i + z[i] - 1;
                        }
                    }
                }
                return z;
            }

            public List<Integer> getStartIndexesWhereWordIsFound(String haku, String kohde) {
                // this is alternative use case
                char[] s = new char[haku.length() + 1 + kohde.length()];
                for (int i=0; i<haku.length(); i++) {
                    s[i] = haku.charAt(i);
                }
                int j = haku.length();
                s[j++] = '#';
                for (int i=0; i<kohde.length(); i++) {
                    s[j++] = kohde.charAt(i);
                }
                int[] z = toZarray(s);
                List<Integer> indexes = new ArrayList<>();
                for (int i=haku.length(); i<z.length; i++) {
                    if (z[i] < haku.length()) continue;
                    indexes.add(i);
                }
                return indexes;
            }

        }

        class StringHasher {

            class HashedString {
                long[] hashes;
                long[] modifiers;

                public HashedString(long[] hashes, long[] modifiers) {
                    this.hashes = hashes;
                    this.modifiers = modifiers;
                }
            }

            long P;
            long M;

            public StringHasher() {
                initializePandM();
            }

            HashedString hashString(String s) {
                int n = s.length();
                long[] hashes = new long[n];
                long[] modifiers = new long[n];
                hashes[0] = s.charAt(0);
                modifiers[0] = 1;
                for (int i=1; i<n; i++) {
                    hashes[i] = (hashes[i-1] * P + s.charAt(i)) % M;
                    modifiers[i] = (modifiers[i-1] * P) % M;
                }
                return new HashedString(hashes, modifiers);
            }

            /**
             * Indices are inclusive.
             */
            long getHash(HashedString hashedString, int startIndex, int endIndex) {
                long[] hashes = hashedString.hashes;
                long[] modifiers = hashedString.modifiers;
                long result = hashes[endIndex];
                if (startIndex > 0) result -= (hashes[startIndex-1] * modifiers[endIndex-startIndex+1]) % M;
                if (result < 0) result += M;
                return result;
            }

            // Less interesting methods below

            /**
             * Efficient for 2 input parameter strings in particular.
             */
            HashedString[] hashString(String first, String second) {
                HashedString[] array = new HashedString[2];
                int n = first.length();
                long[] modifiers = new long[n];
                modifiers[0] = 1;

                long[] firstHashes = new long[n];
                firstHashes[0] = first.charAt(0);
                array[0] = new HashedString(firstHashes, modifiers);

                long[] secondHashes = new long[n];
                secondHashes[0] = second.charAt(0);
                array[1] = new HashedString(secondHashes, modifiers);

                for (int i=1; i<n; i++) {
                    modifiers[i] = (modifiers[i-1] * P) % M;
                    firstHashes[i] = (firstHashes[i-1] * P + first.charAt(i)) % M;
                    secondHashes[i] = (secondHashes[i-1] * P + second.charAt(i)) % M;
                }
                return array;
            }

            /**
             * Efficient for 3+ strings
             * More efficient than multiple hashString calls IF strings are same length.
             */
            HashedString[] hashString(String... strings) {
                HashedString[] array = new HashedString[strings.length];
                int n = strings[0].length();
                long[] modifiers = new long[n];
                modifiers[0] = 1;
                for (int j=0; j<strings.length; j++) {
                    // if all strings are not same length, defer work to another method
                    if (strings[j].length() != n) {
                        for (int i=0; i<n; i++) {
                            array[i] = hashString(strings[i]);
                        }
                        return array;
                    }

                    // otherwise initialize stuff
                    long[] hashes = new long[n];
                    hashes[0] = strings[j].charAt(0);
                    array[j] = new HashedString(hashes, modifiers);
                }
                for (int i=1; i<n; i++) {
                    modifiers[i] = (modifiers[i-1] * P) % M;
                    for (int j=0; j<strings.length; j++) {
                        String s = strings[j];
                        long[] hashes = array[j].hashes;
                        hashes[i] = (hashes[i-1] * P + s.charAt(i)) % M;
                    }
                }
                return array;
            }

            void initializePandM() {
                ArrayList<Long> modOptions = new ArrayList<>(20);
                modOptions.add(353873237L);
                modOptions.add(353875897L);
                modOptions.add(353878703L);
                modOptions.add(353882671L);
                modOptions.add(353885303L);
                modOptions.add(353888377L);
                modOptions.add(353893457L);
                P = modOptions.get(new Random().nextInt(modOptions.size()));

                modOptions.clear();
                modOptions.add(452940277L);
                modOptions.add(452947687L);
                modOptions.add(464478431L);
                modOptions.add(468098221L);
                modOptions.add(470374601L);
                modOptions.add(472879717L);
                modOptions.add(472881973L);
                M = modOptions.get(new Random().nextInt(modOptions.size()));

            }
        }

        int editDistance(String a, String b) {
            a = "#"+a;
            b = "#"+b;
            int n = a.length();
            int m = b.length();
            int[][] dp = new int[n+1][m+1];
            for (int y=0; y<=n; y++) {
                for (int x=0; x<=m; x++) {
                    if (y == 0) dp[y][x] = x;
                    else if (x == 0) dp[y][x] = y;
                    else {
                        int e1 = dp[y-1][x] + 1;
                        int e2 = dp[y][x-1] + 1;
                        int e3 = dp[y-1][x-1] + (a.charAt(y-1) != b.charAt(x-1) ? 1 : 0);
                        dp[y][x] = min(e1, e2, e3);
                    }
                }
            }
            return dp[n][m];
        }

        /*************************** Technical ***************************/

        private class IO extends PrintWriter {
            private InputStreamReader r;
            private static final int BUFSIZE = 1 << 15;
            private char[] buf;
            private int bufc;
            private int bufi;
            private StringBuilder sb;

            public IO() {
                super(new BufferedOutputStream(System.out));
                r = new InputStreamReader(System.in);

                buf = new char[BUFSIZE];
                bufc = 0;
                bufi = 0;
                sb = new StringBuilder();
            }

            /** Print, flush, return nextInt. */
            private int queryInt(String s) {
                io.println(s);
                io.flush();
                return nextInt();
            }

            /** Print, flush, return nextLong. */
            private long queryLong(String s) {
                io.println(s);
                io.flush();
                return nextLong();
            }

            /** Print, flush, return next word. */
            private String queryNext(String s) {
                io.println(s);
                io.flush();
                return next();
            }

            private void fillBuf() throws IOException {
                bufi = 0;
                bufc = 0;
                while(bufc == 0) {
                    bufc = r.read(buf, 0, BUFSIZE);
                    if(bufc == -1) {
                        bufc = 0;
                        return;
                    }
                }
            }

            private boolean pumpBuf() throws IOException {
                if(bufi == bufc) {
                    fillBuf();
                }
                return bufc != 0;
            }

            private boolean isDelimiter(char c) {
                return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f';
            }

            private void eatDelimiters() throws IOException {
                while(true) {
                    if(bufi == bufc) {
                        fillBuf();
                        if(bufc == 0) throw new RuntimeException("IO: Out of input.");
                    }

                    if(!isDelimiter(buf[bufi])) break;
                    ++bufi;
                }
            }

            public String next() {
                try {
                    sb.setLength(0);

                    eatDelimiters();
                    int start = bufi;

                    while(true) {
                        if(bufi == bufc) {
                            sb.append(buf, start, bufi - start);
                            fillBuf();
                            start = 0;
                            if(bufc == 0) break;
                        }

                        if(isDelimiter(buf[bufi])) break;
                        ++bufi;
                    }

                    sb.append(buf, start, bufi - start);

                    return sb.toString();
                } catch(IOException e) {
                    throw new RuntimeException("IO.next: Caught IOException.");
                }
            }

            public int nextInt() {
                try {
                    int ret = 0;

                    eatDelimiters();

                    boolean positive = true;
                    if(buf[bufi] == '-') {
                        ++bufi;
                        if(!pumpBuf()) throw new RuntimeException("IO.nextInt: Invalid int.");
                        positive = false;
                    }

                    boolean first = true;
                    while(true) {
                        if(!pumpBuf()) break;
                        if(isDelimiter(buf[bufi])) {
                            if(first) throw new RuntimeException("IO.nextInt: Invalid int.");
                            break;
                        }
                        first = false;

                        if(buf[bufi] >= '0' && buf[bufi] <= '9') {
                            if(ret < -214748364) throw new RuntimeException("IO.nextInt: Invalid int.");
                            ret *= 10;
                            ret -= (int)(buf[bufi] - '0');
                            if(ret > 0) throw new RuntimeException("IO.nextInt: Invalid int.");
                        } else {
                            throw new RuntimeException("IO.nextInt: Invalid int.");
                        }

                        ++bufi;
                    }

                    if(positive) {
                        if(ret == -2147483648) throw new RuntimeException("IO.nextInt: Invalid int.");
                        ret = -ret;
                    }

                    return ret;
                } catch(IOException e) {
                    throw new RuntimeException("IO.nextInt: Caught IOException.");
                }
            }

            public long nextLong() {
                try {
                    long ret = 0;

                    eatDelimiters();

                    boolean positive = true;
                    if(buf[bufi] == '-') {
                        ++bufi;
                        if(!pumpBuf()) throw new RuntimeException("IO.nextLong: Invalid long.");
                        positive = false;
                    }

                    boolean first = true;
                    while(true) {
                        if(!pumpBuf()) break;
                        if(isDelimiter(buf[bufi])) {
                            if(first) throw new RuntimeException("IO.nextLong: Invalid long.");
                            break;
                        }
                        first = false;

                        if(buf[bufi] >= '0' && buf[bufi] <= '9') {
                            if(ret < -922337203685477580L) throw new RuntimeException("IO.nextLong: Invalid long.");
                            ret *= 10;
                            ret -= (long)(buf[bufi] - '0');
                            if(ret > 0) throw new RuntimeException("IO.nextLong: Invalid long.");
                        } else {
                            throw new RuntimeException("IO.nextLong: Invalid long.");
                        }

                        ++bufi;
                    }

                    if(positive) {
                        if(ret == -9223372036854775808L) throw new RuntimeException("IO.nextLong: Invalid long.");
                        ret = -ret;
                    }

                    return ret;
                } catch(IOException e) {
                    throw new RuntimeException("IO.nextLong: Caught IOException.");
                }
            }

            public double nextDouble() {
                return Double.parseDouble(next());
            }

        }

        void print(Object output) {
            io.println(output);
        }

        void done(Object output) {
            print(output);
            done();
        }

        void done() {
            io.close();
            throw new RuntimeException("Clean exit");
        }

        long min(long... v) {
            long ans = v[0];
            for (int i=1; i<v.length; i++) {
                ans = Math.min(ans, v[i]);
            }
            return ans;
        }

        double min(double... v) {
            double ans = v[0];
            for (int i=1; i<v.length; i++) {
                ans = Math.min(ans, v[i]);
            }
            return ans;
        }

        int min(int... v) {
            int ans = v[0];
            for (int i=1; i<v.length; i++) {
                ans = Math.min(ans, v[i]);
            }
            return ans;
        }

        long max(long... v) {
            long ans = v[0];
            for (int i=1; i<v.length; i++) {
                ans = Math.max(ans, v[i]);
            }
            return ans;
        }

        double max(double... v) {
            double ans = v[0];
            for (int i=1; i<v.length; i++) {
                ans = Math.max(ans, v[i]);
            }
            return ans;
        }

        int max(int... v) {
            int ans = v[0];
            for (int i=1; i<v.length; i++) {
                ans = Math.max(ans, v[i]);
            }
            return ans;
        }

    }

}

//package baobab;

import java.io.*;
import java.util.*;

public class D {

    public static void main(String[] args) {
        Solver solver = new Solver();
    }

    static class Solver {
        IO io;

        public Solver() {
            this.io = new IO();
            try {
                solve();
            } catch (RuntimeException e) {
                if (!e.getMessage().equals("Clean exit")) {
                    throw e;
                }
            } finally {
                io.close();
            }
        }

        /****************************** START READING HERE ********************************/

        void solve() {
            int n = io.nextInt();
            int[][] b = new int[n][n];
            for (int y=0; y<n; y++) {
                for (int x=0; x<n; x++) {
                    b[y][x] = io.nextInt();
                }
            }
            int end = n*n;
            int[] x = new int[end+1];
            int[] y = new int[end+1];
            for (int i=0; i<n; i++) {
                for (int j=0; j<n; j++) {
                    int v = b[i][j];
                    y[v] = i;
                    x[v] = j;
                }
            }

            State ans = null;
            State[][][][][] leastSteps = new State[n][n][n][n][3];
            Deque<State> q = new ArrayDeque<>();
            q.add(new State(y[1], x[1], y[2], x[2], ROOK, 0, 0));
            q.add(new State(y[1], x[1], y[2], x[2], KNIGHT, 0, 0));
            q.add(new State(y[1], x[1], y[2], x[2], BISHOP, 0, 0));
            while (!q.isEmpty()) {
                State s = q.pollFirst();
                if (s.currY < 0 || s.currX < 0 || s.currY >= n || s.currX >= n) continue;
                State prev = leastSteps[s.currY][s.currX][s.goalY][s.goalX][s.piece];
                if (prev != null) {
                    if (prev.steps < s.steps) continue;
                    if (prev.steps == s.steps && prev.replacements <= s.replacements) continue;
                }
                leastSteps[s.currY][s.currX][s.goalY][s.goalX][s.piece] = s;

                // intermediary goal reached?
                if (s.currX == s.goalX && s.currY == s.goalY) {

                    if (b[s.currY][s.currX] == end) {
                        // actually reached end state with fewest steps but not necessarily fewest replacements for that step size
                        if (ans == null) ans = s;
                        else if (ans.replacements >= s.replacements) ans = s;
                        continue; // stop bfs but dont break because we may find new solution with same steps but fewer replacements
                    }

                    // change state to set next goal.
                    int prevGoal = b[s.goalY][s.goalX];
                    int newGoal = prevGoal + 1;
                    int newGoalX = x[newGoal];
                    int newGoalY = y[newGoal];
                    // front of the queue!
                    q.addFirst(new State(s.goalY, s.goalX, newGoalY, newGoalX, s.piece, s.steps, s.replacements));
                    continue;
                }

                // change piece?
                q.addLast(new State(s.currY, s.currX, s.goalY, s.goalX, ROOK, s.steps+1, s.replacements+1));
                q.addLast(new State(s.currY, s.currX, s.goalY, s.goalX, BISHOP, s.steps+1, s.replacements+1));
                q.addLast(new State(s.currY, s.currX, s.goalY, s.goalX, KNIGHT, s.steps+1, s.replacements+1));

                // move current piece?
                if (s.piece == ROOK) {
                    for (int newY=0; newY<n; newY++) q.addLast(new State(newY, s.currX, s.goalY, s.goalX, s.piece, s.steps+1, s.replacements));
                    for (int newX=0; newX<n; newX++) q.addLast(new State(s.currY, newX, s.goalY, s.goalX, s.piece, s.steps+1, s.replacements));
                } else if (s.piece == KNIGHT) {
                    int newY = s.currY + 1;
                    int newX = s.currX + 2;
                    q.addLast(new State(newY, newX, s.goalY, s.goalX, s.piece, s.steps+1, s.replacements));
                    newY = s.currY + 1;
                    newX = s.currX - 2;
                    q.addLast(new State(newY, newX, s.goalY, s.goalX, s.piece, s.steps+1, s.replacements));
                    newY = s.currY - 1;
                    newX = s.currX - 2;
                    q.addLast(new State(newY, newX, s.goalY, s.goalX, s.piece, s.steps+1, s.replacements));
                    newY = s.currY - 1;
                    newX = s.currX + 2;
                    q.addLast(new State(newY, newX, s.goalY, s.goalX, s.piece, s.steps+1, s.replacements));
                    newY = s.currY + 2;
                    newX = s.currX + 1;
                    q.addLast(new State(newY, newX, s.goalY, s.goalX, s.piece, s.steps+1, s.replacements));
                    newY = s.currY - 2;
                    newX = s.currX + 1;
                    q.addLast(new State(newY, newX, s.goalY, s.goalX, s.piece, s.steps+1, s.replacements));
                    newY = s.currY + 2;
                    newX = s.currX - 1;
                    q.addLast(new State(newY, newX, s.goalY, s.goalX, s.piece, s.steps+1, s.replacements));
                    newY = s.currY - 2;
                    newX = s.currX - 1;
                    q.addLast(new State(newY, newX, s.goalY, s.goalX, s.piece, s.steps+1, s.replacements));

                } else if (s.piece == BISHOP) {
                    int newY = s.currY;
                    int newX = s.currX;
                    while (true) {
                        newY += 1;
                        newX += 1;
                        if (newX >= n || newX < 0 || newY >= n || newY < 0) break;
                        q.addLast(new State(newY, newX, s.goalY, s.goalX, s.piece, s.steps+1, s.replacements));
                    }
                    newY = s.currY;
                    newX = s.currX;
                    while (true) {
                        newY += 1;
                        newX -= 1;
                        if (newX >= n || newX < 0 || newY >= n || newY < 0) break;
                        q.addLast(new State(newY, newX, s.goalY, s.goalX, s.piece, s.steps+1, s.replacements));
                    }
                    newY = s.currY;
                    newX = s.currX;
                    while (true) {
                        newY -= 1;
                        newX -= 1;
                        if (newX >= n || newX < 0 || newY >= n || newY < 0) break;
                        q.addLast(new State(newY, newX, s.goalY, s.goalX, s.piece, s.steps+1, s.replacements));
                    }
                    newY = s.currY;
                    newX = s.currX;
                    while (true) {
                        newY -= 1;
                        newX += 1;
                        if (newX >= n || newX < 0 || newY >= n || newY < 0) break;
                        q.addLast(new State(newY, newX, s.goalY, s.goalX, s.piece, s.steps+1, s.replacements));
                    }
                }
            }

            io.println(ans.steps + " " + ans.replacements);
        }

        static int ROOK = 0;
        static int KNIGHT = 1;
        static int BISHOP = 2;

        class State {
            int currY;
            int currX;
            int goalY;
            int goalX;
            int piece;
            int steps;
            int replacements;

            public State(int currY, int currX, int goalY, int goalX, int piece, int steps, int replacements) {
                this.currY = currY;
                this.currX = currX;
                this.goalY = goalY;
                this.goalX = goalX;
                this.piece = piece;
                this.steps = steps;
                this.replacements = replacements;
            }

            @Override
            public boolean equals(Object o) {
                if (this == o) return true;
                if (o == null || getClass() != o.getClass()) return false;
                State state = (State) o;
                return currY == state.currY &&
                        currX == state.currX &&
                        goalY == state.goalY &&
                        goalX == state.goalX &&
                        piece == state.piece;
            }

            @Override
            public int hashCode() {
                return Objects.hash(currY, currX, goalY, goalX, piece);
            }
        }

        /************************** UTILITY CODE BELOW THIS LINE **************************/

        long MOD = (long)1e9 + 7;

        boolean closeToZero(double v) {
            // Check if double is close to zero, considering precision issues.
            return Math.abs(v) <= 0.0000000001;
        }

        class DrawGrid {

            void draw(boolean[][] d) {
                System.out.print("  ");
                for (int x=0; x<d[0].length; x++) {
                    System.out.print(" " + x + " ");
                }
                System.out.println("");
                for (int y=0; y<d.length; y++) {
                    System.out.print(y + " ");
                    for (int x=0; x<d[0].length; x++) {
                        System.out.print((d[y][x] ? "[x]" : "[ ]"));
                    }
                    System.out.println("");
                }
            }

            void draw(int[][] d) {
                int max = 1;
                for (int y=0; y<d.length; y++) {
                    for (int x=0; x<d[0].length; x++) {
                        max = Math.max(max, ("" + d[y][x]).length());
                    }
                }
                System.out.print("  ");
                String format = "%" + (max+2) + "s";
                for (int x=0; x<d[0].length; x++) {
                    System.out.print(String.format(format, x) + " ");
                }
                format = "%" + (max) + "s";
                System.out.println("");
                for (int y=0; y<d.length; y++) {
                    System.out.print(y + " ");
                    for (int x=0; x<d[0].length; x++) {
                        System.out.print(" [" + String.format(format, (d[y][x])) + "]");
                    }
                    System.out.println("");
                }
            }

        }

        class IDval implements Comparable<IDval> {
            int steps;
            long replacements;

            public IDval(int steps, long replacements) {
                this.replacements = replacements;
                this.steps = steps;
            }

            @Override
            public int compareTo(IDval o) {
                if (this.replacements < o.replacements) return -1;
                if (this.replacements > o.replacements) return 1;
                return this.steps - o.steps;
            }
        }

        private class ElementCounter {
            private HashMap<Long, Integer> elements;

            public ElementCounter() {
                elements = new HashMap<>();
            }

            public void add(long element) {
                int count = 1;
                Integer prev = elements.get(element);
                if (prev != null) count += prev;
                elements.put(element, count);
            }

            public void remove(long element) {
                int count = elements.remove(element);
                count--;
                if (count > 0) elements.put(element, count);
            }

            public int get(long element) {
                Integer val = elements.get(element);
                if (val == null) return 0;
                return val;
            }

            public int size() {
                return elements.size();
            }
        }

        class StringCounter {
            HashMap<String, Integer> elements;

            public StringCounter() {
                elements = new HashMap<>();
            }

            public void add(String identifier) {
                int count = 1;
                Integer prev = elements.get(identifier);
                if (prev != null) count += prev;
                elements.put(identifier, count);
            }

            public void remove(String identifier) {
                int count = elements.remove(identifier);
                count--;
                if (count > 0) elements.put(identifier, count);
            }

            public long get(String identifier) {
                Integer val = elements.get(identifier);
                if (val == null) return 0;
                return val;
            }

            public int size() {
                return elements.size();
            }
        }

        class DisjointSet {
            /** Union Find / Disjoint Set data structure. */
            int[] size;
            int[] parent;
            int componentCount;

            public DisjointSet(int n) {
                componentCount = n;
                size = new int[n];
                parent = new int[n];
                for (int i=0; i<n; i++) parent[i] = i;
                for (int i=0; i<n; i++) size[i] = 1;
            }

            public void join(int a, int b) {
                /* Find roots */
                int rootA = parent[a];
                int rootB = parent[b];
                while (rootA != parent[rootA]) rootA = parent[rootA];
                while (rootB != parent[rootB]) rootB = parent[rootB];

                if (rootA == rootB) {
                    /* Already in the same set */
                    return;
                }

                /* Merge smaller set into larger set. */
                if (size[rootA] > size[rootB]) {
                    size[rootA] += size[rootB];
                    parent[rootB] = rootA;
                } else {
                    size[rootB] += size[rootA];
                    parent[rootA] = rootB;
                }
                componentCount--;
            }

        }

        class Trie {
            int N;
            int Z;
            int nextFreeId;
            int[][] pointers;
            boolean[] end;

            /** maxLenSum = maximum possible sum of length of words */
            public Trie(int maxLenSum, int alphabetSize) {
                this.N = maxLenSum;
                this.Z = alphabetSize;
                this.nextFreeId = 1;
                pointers = new int[N+1][alphabetSize];
                end = new boolean[N+1];
            }

            public void addWord(String word) {
                int curr = 0;
                for (int j=0; j<word.length(); j++) {
                    int c = word.charAt(j) - 'a';
                    int next = pointers[curr][c];
                    if (next == 0) {
                        next = nextFreeId++;
                        pointers[curr][c] = next;
                    }
                    curr = next;
                }
                end[curr] = true;
            }

            public boolean hasWord(String word) {
                int curr = 0;
                for (int j=0; j<word.length(); j++) {
                    int c = word.charAt(j) - 'a';
                    int next = pointers[curr][c];
                    if (next == 0) return false;
                    curr = next;
                }
                return end[curr];
            }

        }

        private static class Prob {

            /** For heavy calculations on probabilities, this class
             *  provides more accuracy & efficiency than doubles.
             *  Math explained: https://en.wikipedia.org/wiki/Log_probability
             *  Quick start:
             *      - Instantiate probabilities, eg. Prob a = new Prob(0.75)
             *      - add(), multiply() return new objects, can perform on nulls & NaNs.
             *      - get() returns probability as a readable double */

            /** Logarithmized probability. Note: 0% represented by logP NaN. */
            private double logP;

            /** Construct instance with real probability. */
            public Prob(double real) {
                if (real > 0) this.logP = Math.log(real);
                else this.logP = Double.NaN;
            }

            /** Construct instance with already logarithmized value. */
            static boolean dontLogAgain = true;
            public Prob(double logP, boolean anyBooleanHereToChooseThisConstructor) {
                this.logP = logP;
            }

            /** Returns real probability as a double. */
            public double get() {
                return Math.exp(logP);
            }

            @Override
            public String toString() {
                return ""+get();
            }

            /***************** STATIC METHODS BELOW ********************/

            /** Note: returns NaN only when a && b are both NaN/null. */
            public static Prob add(Prob a, Prob b) {
                if (nullOrNaN(a) && nullOrNaN(b)) return new Prob(Double.NaN, dontLogAgain);
                if (nullOrNaN(a)) return copy(b);
                if (nullOrNaN(b)) return copy(a);

                double x = Math.max(a.logP, b.logP);
                double y = Math.min(a.logP, b.logP);
                double sum = x + Math.log(1 + Math.exp(y - x));
                return new Prob(sum, dontLogAgain);
            }

            /** Note: multiplying by null or NaN produces NaN (repping 0% real prob). */
            public static Prob multiply(Prob a, Prob b) {
                if (nullOrNaN(a) || nullOrNaN(b)) return new Prob(Double.NaN, dontLogAgain);
                return new Prob(a.logP + b.logP, dontLogAgain);
            }

            /** Returns true if p is null or NaN. */
            private static boolean nullOrNaN(Prob p) {
                return (p == null || Double.isNaN(p.logP));
            }

            /** Returns a new instance with the same value as original. */
            private static Prob copy(Prob original) {
                return new Prob(original.logP, dontLogAgain);
            }
        }

        class Binary implements Comparable<Binary> {

            /**
             * Use example: Binary b = new Binary(Long.toBinaryString(53249834L));
             *
             * When manipulating small binary strings, instantiate new Binary(string)
             * When just reading large binary strings, instantiate new Binary(string,true)
             * get(int i) returns a character '1' or '0', not an int.
             */

            private boolean[] d;
            private int first; // Starting from left, the first (most remarkable) '1'
            public int length;

            public Binary(String binaryString) {
                this(binaryString, false);
            }
            public Binary(String binaryString, boolean initWithMinArraySize) {
                length = binaryString.length();
                int size = Math.max(2*length, 1);
                first = length/4;
                if (initWithMinArraySize) {
                    first = 0;
                    size = Math.max(length, 1);
                }
                d = new boolean[size];
                for (int i=0; i<length; i++) {
                    if (binaryString.charAt(i) == '1') d[i+first] = true;
                }
            }

            public void addFirst(char c) {
                if (first-1 < 0) doubleArraySize();
                first--;
                d[first] = (c == '1' ? true : false);
                length++;
            }

            public void addLast(char c) {
                if (first+length >= d.length) doubleArraySize();
                d[first+length] = (c == '1' ? true : false);
                length++;
            }

            private void doubleArraySize() {
                boolean[] bigArray = new boolean[(d.length+1) * 2];
                int newFirst = bigArray.length / 4;
                for (int i=0; i<length; i++) {
                    bigArray[i + newFirst] = d[i + first];
                }
                first = newFirst;
                d = bigArray;
            }

            public boolean flip(int i) {
                boolean value = (this.d[first+i] ? false : true);
                this.d[first+i] = value;
                return value;
            }

            public void set(int i, char c) {
                boolean value = (c == '1' ? true : false);
                this.d[first+i] = value;
            }

            public char get(int i) {
                return (this.d[first+i] ? '1' : '0');
            }

            @Override
            public int compareTo(Binary o) {
                if (this.length != o.length) return this.length - o.length;
                int len = this.length;
                for (int i=0; i<len; i++) {
                    int diff = this.get(i) - o.get(i);
                    if (diff != 0) return diff;
                }
                return 0;
            }

            @Override
            public String toString() {
                StringBuilder sb = new StringBuilder();
                for (int i=0; i<length; i++) {
                    sb.append(d[i+first] ? '1' : '0');
                }
                return sb.toString();
            }

        }

        /************************** Range queries **************************/

        class FenwickMin {
            long n;
            long[] original;
            long[] bottomUp;
            long[] topDown;

            public FenwickMin(int n) {
                this.n = n;
                original = new long[n+2];
                bottomUp = new long[n+2];
                topDown = new long[n+2];
            }

            public void set(int modifiedNode, long value) {
                long replaced = original[modifiedNode];
                original[modifiedNode] = value;
                // Update left tree
                int i = modifiedNode;
                long v = value;
                while (i <= n) {
                    if (v > bottomUp[i]) {
                        if (replaced == bottomUp[i]) {
                            v = Math.min(v, original[i]);
                            for (int r=1 ;; r++) {
                                int x = (i&-i)>>>r;
                                if (x == 0) break;
                                int child = i-x;
                                v = Math.min(v, bottomUp[child]);
                            }
                        } else break;
                    }
                    if (v == bottomUp[i]) break;
                    bottomUp[i] = v;
                    i += (i&-i);
                }
                // Update right tree
                i = modifiedNode;
                v = value;
                while (i > 0) {
                    if (v > topDown[i]) {
                        if (replaced == topDown[i]) {
                            v = Math.min(v, original[i]);
                            for (int r=1 ;; r++) {
                                int x = (i&-i)>>>r;
                                if (x == 0) break;
                                int child = i+x;
                                if (child > n+1) break;
                                v = Math.min(v, topDown[child]);
                            }
                        } else break;
                    }
                    if (v == topDown[i]) break;
                    topDown[i] = v;
                    i -= (i&-i);
                }
            }

            public long getMin(int a, int b) {
                long min = original[a];
                int prev = a;
                int curr = prev + (prev&-prev); // parent right hand side
                while (curr <= b) {
                    min = Math.min(min, topDown[prev]); // value from the other tree
                    prev = curr;
                    curr = prev + (prev&-prev);;
                }
                min = Math.min(min, original[prev]);
                prev = b;
                curr = prev - (prev&-prev); // parent left hand side
                while (curr >= a) {
                    min = Math.min(min,bottomUp[prev]); // value from the other tree
                    prev = curr;
                    curr = prev - (prev&-prev);
                }
                return min;
            }

        }

        class FenwickSum {
            public long[] d;

            public FenwickSum(int n) {
                d=new long[n+1];
            }

            /** a[0] must be unused. */
            public FenwickSum(long[] a) {
                d=new long[a.length];
                for (int i=1; i<a.length; i++) {
                    modify(i, a[i]);
                }
            }

            /** Do not modify i=0. */
            void modify(int i, long v) {
                while (i<d.length) {
                    d[i] += v;
                    // Move to next uplink on the RIGHT side of i
                    i += (i&-i);
                }
            }

            /** Returns sum from a to b, *BOTH* inclusive. */
            long getSum(int a, int b) {
                return getSum(b) - getSum(a-1);
            }

            private long getSum(int i) {
                long sum = 0;
                while (i>0) {
                    sum += d[i];
                    // Move to next uplink on the LEFT side of i
                    i -= (i&-i);
                }
                return sum;
            }
        }

        class SegmentTree {

            /* Provides log(n) operations for:
             * - Range query (sum, min or max)
             * - Range update ("+8 to all values between indexes 4 and 94")
             */

            int N;
            long[] lazy;
            long[] sum;
            long[] min;
            long[] max;
            boolean supportSum;
            boolean supportMin;
            boolean supportMax;

            public SegmentTree(int n) {
                this(n, true, true, true);
            }

            public SegmentTree(int n, boolean supportSum, boolean supportMin, boolean supportMax) {
                for (N=2; N<n;) N*=2;
                this.lazy = new long[2*N];
                this.supportSum = supportSum;
                this.supportMin = supportMin;
                this.supportMax = supportMax;
                if (this.supportSum) this.sum = new long[2*N];
                if (this.supportMin) this.min = new long[2*N];
                if (this.supportMax) this.max = new long[2*N];
            }

            void modifyRange(long x, int a, int b) {
                modifyRec(a, b, 1, 0, N-1, x);
            }

            void setRange() {
                //TODO
            }

            long getSum(int a, int b) {
                return querySum(a, b, 1, 0, N-1);
            }

            long getMin(int a, int b) {
                return queryMin(a, b, 1, 0, N-1);
            }

            long getMax(int a, int b) {
                return queryMax(a, b, 1, 0, N-1);
            }

            private long querySum(int wantedLeft, int wantedRight, int i, int actualLeft, int actualRight) {
                if (wantedLeft > actualRight || wantedRight < actualLeft) {
                    return 0;
                }
                if (wantedLeft == actualLeft && wantedRight == actualRight) {
                    int count = wantedRight - wantedLeft + 1;
                    return sum[i] + count * lazy[i];
                }
                if (lazy[i] != 0) propagate(i, actualLeft, actualRight);
                int d = (actualRight - actualLeft + 1) / 2;
                long left = querySum(wantedLeft, min(actualLeft+d-1, wantedRight), 2*i, actualLeft, actualLeft+d-1);
                long right = querySum(max(actualLeft+d, wantedLeft), wantedRight, 2*i+1, actualLeft+d, actualRight);
                return left + right;
            }

            private long queryMin(int wantedLeft, int wantedRight, int i, int actualLeft, int actualRight) {
                if (wantedLeft > actualRight || wantedRight < actualLeft) {
                    return 0;
                }
                if (wantedLeft == actualLeft && wantedRight == actualRight) {
                    return min[i] + lazy[i];
                }
                if (lazy[i] != 0) propagate(i, actualLeft, actualRight);
                int d = (actualRight - actualLeft + 1) / 2;
                long left = queryMin(wantedLeft, min(actualLeft+d-1, wantedRight), 2*i, actualLeft, actualLeft+d-1);
                long right = queryMin(max(actualLeft+d, wantedLeft), wantedRight, 2*i+1, actualLeft+d, actualRight);
                return min(left, right);
            }

            private long queryMax(int wantedLeft, int wantedRight, int i, int actualLeft, int actualRight) {
                if (wantedLeft > actualRight || wantedRight < actualLeft) {
                    return 0;
                }
                if (wantedLeft == actualLeft && wantedRight == actualRight) {
                    return max[i] + lazy[i];
                }
                if (lazy[i] != 0) propagate(i, actualLeft, actualRight);
                int d = (actualRight - actualLeft + 1) / 2;
                long left = queryMax(wantedLeft, min(actualLeft+d-1, wantedRight), 2*i, actualLeft, actualLeft+d-1);
                long right = queryMax(max(actualLeft+d, wantedLeft), wantedRight, 2*i+1, actualLeft+d, actualRight);
                return max(left, right);
            }

            private void modifyRec(int wantedLeft, int wantedRight, int i, int actualLeft, int actualRight, long value) {
                if (wantedLeft > actualRight || wantedRight < actualLeft) {
                    return;
                }
                if (wantedLeft == actualLeft && wantedRight == actualRight) {
                    lazy[i] += value;
                    return;
                }
                if (lazy[i] != 0) propagate(i, actualLeft, actualRight);
                int d = (actualRight - actualLeft + 1) / 2;
                modifyRec(wantedLeft, min(actualLeft+d-1, wantedRight), 2*i, actualLeft, actualLeft+d-1, value);
                modifyRec(max(actualLeft+d, wantedLeft), wantedRight, 2*i+1, actualLeft+d, actualRight, value);
                if (supportSum) sum[i] += value * (min(actualRight, wantedRight) - max(actualLeft, wantedLeft) + 1);
                if (supportMin) min[i] = min(min[2*i] + lazy[2*i], min[2*i+1] + lazy[2*i+1]);
                if (supportMax) max[i] = max(max[2*i] + lazy[2*i], max[2*i+1] + lazy[2*i+1]);
            }

            private void propagate(int i, int actualLeft, int actualRight) {
                lazy[2*i] += lazy[i];
                lazy[2*i+1] += lazy[i];
                if (supportSum) sum[i] += lazy[i] * (actualRight - actualLeft + 1);
                if (supportMin) min[i] += lazy[i];
                if (supportMax) max[i] += lazy[i];
                lazy[i] = 0;
            }

        }

        /***************************** Graphs *****************************/

        List<Integer>[] toGraph(IO io, int n) {
            List<Integer>[] g = new ArrayList[n+1];
            for (int i=1; i<=n; i++) g[i] = new ArrayList<>();
            for (int i=1; i<=n-1; i++) {
                int a = io.nextInt();
                int b = io.nextInt();
                g[a].add(b);
                g[b].add(a);
            }
            return g;
        }

        class Graph {
            HashMap<Long, List<Long>> edges;

            public Graph() {
                edges = new HashMap<>();
            }

            List<Long> getSetNeighbors(Long node) {
                List<Long> neighbors = edges.get(node);
                if (neighbors == null) {
                    neighbors = new ArrayList<>();
                    edges.put(node, neighbors);
                }
                return neighbors;
            }

            void addBiEdge(Long a, Long b) {
                addEdge(a, b);
                addEdge(b, a);
            }

            void addEdge(Long from, Long to) {
                getSetNeighbors(to); // make sure all have initialized lists
                List<Long> neighbors = getSetNeighbors(from);
                neighbors.add(to);
            }

            // topoSort variables
            int UNTOUCHED = 0;
            int FINISHED = 2;
            int INPROGRESS = 1;
            HashMap<Long, Integer> vis;
            List<Long> topoAns;
            List<Long> failDueToCycle = new ArrayList<Long>() {{ add(-1L); }};

            List<Long> topoSort() {
                topoAns = new ArrayList<>();
                vis = new HashMap<>();
                for (Long a : edges.keySet()) {
                    if (!topoDFS(a)) return failDueToCycle;
                }
                Collections.reverse(topoAns);
                return topoAns;
            }

            boolean topoDFS(long curr) {
                Integer status = vis.get(curr);
                if (status == null) status = UNTOUCHED;
                if (status == FINISHED) return true;
                if (status == INPROGRESS) {
                    return false;
                }
                vis.put(curr, INPROGRESS);
                for (long next : edges.get(curr)) {
                    if (!topoDFS(next)) return false;
                }
                vis.put(curr, FINISHED);
                topoAns.add(curr);
                return true;
            }

        }

        public class StronglyConnectedComponents {

            /** Kosaraju's algorithm */

            ArrayList<Integer>[] forw;
            ArrayList<Integer>[] bacw;

            /** Use: getCount(2, new int[] {1,2}, new int[] {2,1}) */
            public int getCount(int n, int[] mista, int[] minne) {
                forw = new ArrayList[n+1];
                bacw = new ArrayList[n+1];
                for (int i=1; i<=n; i++) {
                    forw[i] = new ArrayList<Integer>();
                    bacw[i] = new ArrayList<Integer>();
                }
                for (int i=0; i<mista.length; i++) {
                    int a = mista[i];
                    int b = minne[i];
                    forw[a].add(b);
                    bacw[b].add(a);
                }
                int count = 0;
                List<Integer> list = new ArrayList<Integer>();
                boolean[] visited = new boolean[n+1];
                for (int i=1; i<=n; i++) {
                    dfsForward(i, visited, list);
                }
                visited = new boolean[n+1];
                for (int i=n-1; i>=0; i--) {
                    int node = list.get(i);
                    if (visited[node]) continue;
                    count++;
                    dfsBackward(node, visited);
                }
                return count;
            }

            public void dfsForward(int i, boolean[] visited, List<Integer> list) {
                if (visited[i]) return;
                visited[i] = true;
                for (int neighbor : forw[i]) {
                    dfsForward(neighbor, visited, list);
                }
                list.add(i);
            }

            public void dfsBackward(int i, boolean[] visited) {
                if (visited[i]) return;
                visited[i] = true;
                for (int neighbor : bacw[i]) {
                    dfsBackward(neighbor, visited);
                }
            }
        }

        class LCAFinder {

            /* O(n log n) Initialize: new LCAFinder(graph)
             * O(log n) Queries: find(a,b) returns lowest common ancestor for nodes a and b */

            int[] nodes;
            int[] depths;
            int[] entries;
            int pointer;
            FenwickMin fenwick;

            public LCAFinder(List<Integer>[] graph) {
                this.nodes = new int[(int)10e6];
                this.depths = new int[(int)10e6];
                this.entries = new int[graph.length];
                this.pointer = 1;
                boolean[] visited = new boolean[graph.length+1];
                dfs(1, 0, graph, visited);
                fenwick = new FenwickMin(pointer-1);
                for (int i=1; i<pointer; i++) {
                    fenwick.set(i, depths[i] * 1000000L + i);
                }
            }

            private void dfs(int node, int depth, List<Integer>[] graph, boolean[] visited) {
                visited[node] = true;
                entries[node] = pointer;
                nodes[pointer] = node;
                depths[pointer] = depth;
                pointer++;
                for (int neighbor : graph[node]) {
                    if (visited[neighbor]) continue;
                    dfs(neighbor, depth+1, graph, visited);
                    nodes[pointer] = node;
                    depths[pointer] = depth;
                    pointer++;
                }
            }

            public int find(int a, int b) {
                int left = entries[a];
                int right = entries[b];
                if (left > right) {
                    int temp = left;
                    left = right;
                    right = temp;
                }
                long mixedBag = fenwick.getMin(left, right);
                int index = (int) (mixedBag % 1000000L);
                return nodes[index];
            }
        }

        /**************************** Geometry ****************************/

        class Point {
            int y;
            int x;

            public Point(int y, int x) {
                this.y = y;
                this.x = x;
            }
        }

        boolean segmentsIntersect(double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4) {
            // Returns true if segment 1-2 intersects segment 3-4

            if (x1 == x2 && x3 == x4) {
                // Both segments are vertical
                if (x1 != x3) return false;
                if (min(y1,y2) < min(y3,y4)) {
                    return max(y1,y2) >= min(y3,y4);
                } else {
                    return max(y3,y4) >= min(y1,y2);
                }
            }
            if (x1 == x2) {
                // Only segment 1-2 is vertical. Does segment 3-4 cross it? y = a*x + b
                double a34 = (y4-y3)/(x4-x3);
                double b34 = y3 - a34*x3;
                double y = a34 * x1 + b34;
                return y >= min(y1,y2) && y <= max(y1,y2) && x1 >= min(x3,x4) && x1 <= max(x3,x4);
            }
            if (x3 == x4) {
                // Only segment 3-4 is vertical. Does segment 1-2 cross it? y = a*x + b
                double a12 = (y2-y1)/(x2-x1);
                double b12 = y1 - a12*x1;
                double y = a12 * x3 + b12;
                return y >= min(y3,y4) && y <= max(y3,y4) && x3 >= min(x1,x2) && x3 <= max(x1,x2);
            }
            double a12 = (y2-y1)/(x2-x1);
            double b12 = y1 - a12*x1;
            double a34 = (y4-y3)/(x4-x3);
            double b34 = y3 - a34*x3;
            if (closeToZero(a12 - a34)) {
                // Parallel lines
                return closeToZero(b12 - b34);
            }
            // Non parallel non vertical lines intersect at x. Is x part of both segments?
            double x = -(b12-b34)/(a12-a34);
            return x >= min(x1,x2) && x <= max(x1,x2) && x >= min(x3,x4) && x <= max(x3,x4);
        }

        boolean pointInsideRectangle(Point p, List<Point> r, boolean countBorderAsInside) {
            Point a = r.get(0);
            Point b = r.get(1);
            Point c = r.get(2);
            Point d = r.get(3);
            double apd = areaOfTriangle(a, p, d);
            double dpc = areaOfTriangle(d, p, c);
            double cpb = areaOfTriangle(c, p, b);
            double pba = areaOfTriangle(p, b, a);
            double sumOfAreas = apd + dpc + cpb + pba;
            if (closeToZero(sumOfAreas - areaOfRectangle(r))) {
                if (closeToZero(apd) || closeToZero(dpc) || closeToZero(cpb) || closeToZero(pba)) {
                    return countBorderAsInside;
                }
                return true;
            }
            return false;
        }

        double areaOfTriangle(Point a, Point b, Point c) {
            return 0.5 * Math.abs((a.x-c.x)*(b.y-a.y)-(a.x-b.x)*(c.y-a.y));
        }

        double areaOfRectangle(List<Point> r) {
            double side1xDiff = r.get(0).x - r.get(1).x;
            double side1yDiff = r.get(0).y - r.get(1).y;
            double side2xDiff = r.get(1).x - r.get(2).x;
            double side2yDiff = r.get(1).y - r.get(2).y;
            double side1 = Math.sqrt(side1xDiff * side1xDiff + side1yDiff * side1yDiff);
            double side2 = Math.sqrt(side2xDiff * side2xDiff + side2yDiff * side2yDiff);
            return side1 * side2;
        }

        boolean pointsOnSameLine(double x1, double y1, double x2, double y2, double x3, double y3) {
            double areaTimes2 = x1 * (y2 - y3) + x2 * (y3 - y1) + x3 * (y1 - y2);
            return (closeToZero(areaTimes2));
        }

        class PointToLineSegmentDistanceCalculator {

            // Just call this
            double minDistFromPointToLineSegment(double point_x, double point_y, double x1, double y1, double x2, double y2) {
                return Math.sqrt(distToSegmentSquared(point_x, point_y, x1, y1, x2, y2));
            }

            private double distToSegmentSquared(double point_x, double point_y, double x1, double y1, double x2, double y2) {
                double l2 = dist2(x1,y1,x2,y2);
                if (l2 == 0) return dist2(point_x, point_y, x1, y1);
                double t = ((point_x - x1) * (x2 - x1) + (point_y - y1) * (y2 - y1)) / l2;
                if (t < 0) return dist2(point_x, point_y, x1, y1);
                if (t > 1) return dist2(point_x, point_y, x2, y2);

                double com_x = x1 + t * (x2 - x1);
                double com_y = y1 + t * (y2 - y1);
                return dist2(point_x, point_y, com_x, com_y);
            }

            private double dist2(double x1, double y1, double x2, double y2) {
                return Math.pow((x1 - x2), 2) + Math.pow((y1 - y2), 2);
            }

        }

        /****************************** Math ******************************/

        long pow(long base, int exp) {
            if (exp == 0) return 1L;
            long x = pow(base, exp/2);
            long ans = x * x;
            if (exp % 2 != 0) ans *= base;
            return ans;
        }

        long gcd(long... v) {
            /** Chained calls to Euclidean algorithm. */
            if (v.length == 1) return v[0];
            long ans = gcd(v[1], v[0]);
            for (int i=2; i<v.length; i++) {
                ans = gcd(ans, v[i]);
            }
            return ans;
        }

        long gcd(long a, long b) {
            /** Euclidean algorithm. */
            if (b == 0) return a;
            return gcd(b, a%b);
        }

        int[] generatePrimesUpTo(int last) {
            /* Sieve of Eratosthenes. Practically O(n). Values of 0 indicate primes. */
            int[] div = new int[last+1];
            for (int x=2; x<=last; x++) {
                if (div[x] > 0) continue;
                for (int u=2*x; u<=last; u+=x) {
                    div[u] = x;
                }
            }
            return div;
        }

        long lcm(long a, long b) {
            /** Least common multiple */
            return a * b / gcd(a,b);
        }

        class BaseConverter {

            /* Palauttaa luvun esityksen kannassa base */
            public String convert(Long number, int base) {
                return Long.toString(number, base);
            }

            /* Palauttaa luvun esityksen kannassa baseTo, kun annetaan luku Stringinä kannassa baseFrom */
            public String convert(String number, int baseFrom, int baseTo) {
                return Long.toString(Long.parseLong(number, baseFrom), baseTo);
            }

            /* Tulkitsee kannassa base esitetyn luvun longiksi (kannassa 10) */
            public long longify(String number, int baseFrom) {
                return Long.parseLong(number, baseFrom);
            }
        }

        class BinomialCoefficients {
            /** Total number of K sized unique combinations from pool of size N (unordered)
             N! / ( K! (N - K)! )   */

            /** For simple queries where output fits in long. */
            public long biCo(long n, long k) {
                long r = 1;
                if (k > n) return 0;
                for (long d = 1; d <= k; d++) {
                    r *= n--;
                    r /= d;
                }
                return r;
            }

            /** For multiple queries with same n, different k. */
            public long[] precalcBinomialCoefficientsK(int n, int maxK) {
                long v[] = new long[maxK+1];
                v[0] = 1; // nC0 == 1
                for (int i=1; i<=n; i++) {
                    for (int j=Math.min(i,maxK); j>0; j--) {
                        v[j] = v[j] + v[j-1]; // Pascal's triangle
                    }
                }
                return v;
            }

            /** When output needs % MOD. */
            public long[] precalcBinomialCoefficientsK(int n, int k, long M) {
                long v[] = new long[k+1];
                v[0] = 1; // nC0 == 1
                for (int i=1; i<=n; i++) {
                    for (int j=Math.min(i,k); j>0; j--) {
                        v[j] = v[j] + v[j-1]; // Pascal's triangle
                        v[j] %= M;
                    }
                }
                return v;
            }
        }

        /**************************** Strings ****************************/

        class Zalgo {

            public int pisinEsiintyma(String haku, String kohde) {
                char[] s = new char[haku.length() + 1 + kohde.length()];
                for (int i=0; i<haku.length(); i++) {
                    s[i] = haku.charAt(i);
                }
                int j = haku.length();
                s[j++] = '#';
                for (int i=0; i<kohde.length(); i++) {
                    s[j++] = kohde.charAt(i);
                }
                int[] z = toZarray(s);
                int max = 0;
                for (int i=haku.length(); i<z.length; i++) {
                    max = Math.max(max, z[i]);
                }
                return max;
            }

            public int[] toZarray(char[] s) {
                int n = s.length;
                int[] z = new int[n];
                int a = 0, b = 0;
                for (int i = 1; i < n; i++) {
                    if (i > b) {
                        for (int j = i; j < n && s[j - i] == s[j]; j++)  z[i]++;
                    }
                    else {
                        z[i] = z[i - a];
                        if (i + z[i - a] > b) {
                            for (int j = b + 1; j < n && s[j - i] == s[j]; j++) z[i]++;
                            a = i;
                            b = i + z[i] - 1;
                        }
                    }
                }
                return z;
            }

            public List<Integer> getStartIndexesWhereWordIsFound(String haku, String kohde) {
                // this is alternative use case
                char[] s = new char[haku.length() + 1 + kohde.length()];
                for (int i=0; i<haku.length(); i++) {
                    s[i] = haku.charAt(i);
                }
                int j = haku.length();
                s[j++] = '#';
                for (int i=0; i<kohde.length(); i++) {
                    s[j++] = kohde.charAt(i);
                }
                int[] z = toZarray(s);
                List<Integer> indexes = new ArrayList<>();
                for (int i=haku.length(); i<z.length; i++) {
                    if (z[i] < haku.length()) continue;
                    indexes.add(i);
                }
                return indexes;
            }

        }

        class StringHasher {

            class HashedString {
                long[] hashes;
                long[] modifiers;

                public HashedString(long[] hashes, long[] modifiers) {
                    this.hashes = hashes;
                    this.modifiers = modifiers;
                }
            }

            long P;
            long M;

            public StringHasher() {
                initializePandM();
            }

            HashedString hashString(String s) {
                int n = s.length();
                long[] hashes = new long[n];
                long[] modifiers = new long[n];
                hashes[0] = s.charAt(0);
                modifiers[0] = 1;
                for (int i=1; i<n; i++) {
                    hashes[i] = (hashes[i-1] * P + s.charAt(i)) % M;
                    modifiers[i] = (modifiers[i-1] * P) % M;
                }
                return new HashedString(hashes, modifiers);
            }

            /**
             * Indices are inclusive.
             */
            long getHash(HashedString hashedString, int startIndex, int endIndex) {
                long[] hashes = hashedString.hashes;
                long[] modifiers = hashedString.modifiers;
                long result = hashes[endIndex];
                if (startIndex > 0) result -= (hashes[startIndex-1] * modifiers[endIndex-startIndex+1]) % M;
                if (result < 0) result += M;
                return result;
            }

            // Less interesting methods below

            /**
             * Efficient for 2 input parameter strings in particular.
             */
            HashedString[] hashString(String first, String second) {
                HashedString[] array = new HashedString[2];
                int n = first.length();
                long[] modifiers = new long[n];
                modifiers[0] = 1;

                long[] firstHashes = new long[n];
                firstHashes[0] = first.charAt(0);
                array[0] = new HashedString(firstHashes, modifiers);

                long[] secondHashes = new long[n];
                secondHashes[0] = second.charAt(0);
                array[1] = new HashedString(secondHashes, modifiers);

                for (int i=1; i<n; i++) {
                    modifiers[i] = (modifiers[i-1] * P) % M;
                    firstHashes[i] = (firstHashes[i-1] * P + first.charAt(i)) % M;
                    secondHashes[i] = (secondHashes[i-1] * P + second.charAt(i)) % M;
                }
                return array;
            }

            /**
             * Efficient for 3+ strings
             * More efficient than multiple hashString calls IF strings are same length.
             */
            HashedString[] hashString(String... strings) {
                HashedString[] array = new HashedString[strings.length];
                int n = strings[0].length();
                long[] modifiers = new long[n];
                modifiers[0] = 1;
                for (int j=0; j<strings.length; j++) {
                    // if all strings are not same length, defer work to another method
                    if (strings[j].length() != n) {
                        for (int i=0; i<n; i++) {
                            array[i] = hashString(strings[i]);
                        }
                        return array;
                    }

                    // otherwise initialize stuff
                    long[] hashes = new long[n];
                    hashes[0] = strings[j].charAt(0);
                    array[j] = new HashedString(hashes, modifiers);
                }
                for (int i=1; i<n; i++) {
                    modifiers[i] = (modifiers[i-1] * P) % M;
                    for (int j=0; j<strings.length; j++) {
                        String s = strings[j];
                        long[] hashes = array[j].hashes;
                        hashes[i] = (hashes[i-1] * P + s.charAt(i)) % M;
                    }
                }
                return array;
            }

            void initializePandM() {
                ArrayList<Long> modOptions = new ArrayList<>(20);
                modOptions.add(353873237L);
                modOptions.add(353875897L);
                modOptions.add(353878703L);
                modOptions.add(353882671L);
                modOptions.add(353885303L);
                modOptions.add(353888377L);
                modOptions.add(353893457L);
                P = modOptions.get(new Random().nextInt(modOptions.size()));

                modOptions.clear();
                modOptions.add(452940277L);
                modOptions.add(452947687L);
                modOptions.add(464478431L);
                modOptions.add(468098221L);
                modOptions.add(470374601L);
                modOptions.add(472879717L);
                modOptions.add(472881973L);
                M = modOptions.get(new Random().nextInt(modOptions.size()));

            }
        }

        int editDistance(String a, String b) {
            a = "#"+a;
            b = "#"+b;
            int n = a.length();
            int m = b.length();
            int[][] dp = new int[n+1][m+1];
            for (int y=0; y<=n; y++) {
                for (int x=0; x<=m; x++) {
                    if (y == 0) dp[y][x] = x;
                    else if (x == 0) dp[y][x] = y;
                    else {
                        int e1 = dp[y-1][x] + 1;
                        int e2 = dp[y][x-1] + 1;
                        int e3 = dp[y-1][x-1] + (a.charAt(y-1) != b.charAt(x-1) ? 1 : 0);
                        dp[y][x] = min(e1, e2, e3);
                    }
                }
            }
            return dp[n][m];
        }

        /*************************** Technical ***************************/

        private class IO extends PrintWriter {
            private InputStreamReader r;
            private static final int BUFSIZE = 1 << 15;
            private char[] buf;
            private int bufc;
            private int bufi;
            private StringBuilder sb;

            public IO() {
                super(new BufferedOutputStream(System.out));
                r = new InputStreamReader(System.in);

                buf = new char[BUFSIZE];
                bufc = 0;
                bufi = 0;
                sb = new StringBuilder();
            }

            /** Print, flush, return nextInt. */
            private int queryInt(String s) {
                io.println(s);
                io.flush();
                return nextInt();
            }

            /** Print, flush, return nextLong. */
            private long queryLong(String s) {
                io.println(s);
                io.flush();
                return nextLong();
            }

            /** Print, flush, return next word. */
            private String queryNext(String s) {
                io.println(s);
                io.flush();
                return next();
            }

            private void fillBuf() throws IOException {
                bufi = 0;
                bufc = 0;
                while(bufc == 0) {
                    bufc = r.read(buf, 0, BUFSIZE);
                    if(bufc == -1) {
                        bufc = 0;
                        return;
                    }
                }
            }

            private boolean pumpBuf() throws IOException {
                if(bufi == bufc) {
                    fillBuf();
                }
                return bufc != 0;
            }

            private boolean isDelimiter(char c) {
                return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f';
            }

            private void eatDelimiters() throws IOException {
                while(true) {
                    if(bufi == bufc) {
                        fillBuf();
                        if(bufc == 0) throw new RuntimeException("IO: Out of input.");
                    }

                    if(!isDelimiter(buf[bufi])) break;
                    ++bufi;
                }
            }

            public String next() {
                try {
                    sb.setLength(0);

                    eatDelimiters();
                    int start = bufi;

                    while(true) {
                        if(bufi == bufc) {
                            sb.append(buf, start, bufi - start);
                            fillBuf();
                            start = 0;
                            if(bufc == 0) break;
                        }

                        if(isDelimiter(buf[bufi])) break;
                        ++bufi;
                    }

                    sb.append(buf, start, bufi - start);

                    return sb.toString();
                } catch(IOException e) {
                    throw new RuntimeException("IO.next: Caught IOException.");
                }
            }

            public int nextInt() {
                try {
                    int ret = 0;

                    eatDelimiters();

                    boolean positive = true;
                    if(buf[bufi] == '-') {
                        ++bufi;
                        if(!pumpBuf()) throw new RuntimeException("IO.nextInt: Invalid int.");
                        positive = false;
                    }

                    boolean first = true;
                    while(true) {
                        if(!pumpBuf()) break;
                        if(isDelimiter(buf[bufi])) {
                            if(first) throw new RuntimeException("IO.nextInt: Invalid int.");
                            break;
                        }
                        first = false;

                        if(buf[bufi] >= '0' && buf[bufi] <= '9') {
                            if(ret < -214748364) throw new RuntimeException("IO.nextInt: Invalid int.");
                            ret *= 10;
                            ret -= (int)(buf[bufi] - '0');
                            if(ret > 0) throw new RuntimeException("IO.nextInt: Invalid int.");
                        } else {
                            throw new RuntimeException("IO.nextInt: Invalid int.");
                        }

                        ++bufi;
                    }

                    if(positive) {
                        if(ret == -2147483648) throw new RuntimeException("IO.nextInt: Invalid int.");
                        ret = -ret;
                    }

                    return ret;
                } catch(IOException e) {
                    throw new RuntimeException("IO.nextInt: Caught IOException.");
                }
            }

            public long nextLong() {
                try {
                    long ret = 0;

                    eatDelimiters();

                    boolean positive = true;
                    if(buf[bufi] == '-') {
                        ++bufi;
                        if(!pumpBuf()) throw new RuntimeException("IO.nextLong: Invalid long.");
                        positive = false;
                    }

                    boolean first = true;
                    while(true) {
                        if(!pumpBuf()) break;
                        if(isDelimiter(buf[bufi])) {
                            if(first) throw new RuntimeException("IO.nextLong: Invalid long.");
                            break;
                        }
                        first = false;

                        if(buf[bufi] >= '0' && buf[bufi] <= '9') {
                            if(ret < -922337203685477580L) throw new RuntimeException("IO.nextLong: Invalid long.");
                            ret *= 10;
                            ret -= (long)(buf[bufi] - '0');
                            if(ret > 0) throw new RuntimeException("IO.nextLong: Invalid long.");
                        } else {
                            throw new RuntimeException("IO.nextLong: Invalid long.");
                        }

                        ++bufi;
                    }

                    if(positive) {
                        if(ret == -9223372036854775808L) throw new RuntimeException("IO.nextLong: Invalid long.");
                        ret = -ret;
                    }

                    return ret;
                } catch(IOException e) {
                    throw new RuntimeException("IO.nextLong: Caught IOException.");
                }
            }

            public double nextDouble() {
                return Double.parseDouble(next());
            }

        }

        void print(Object output) {
            io.println(output);
        }

        void done(Object output) {
            print(output);
            done();
        }

        void done() {
            io.close();
            throw new RuntimeException("Clean exit");
        }

        long min(long... v) {
            long ans = v[0];
            for (int i=1; i<v.length; i++) {
                ans = Math.min(ans, v[i]);
            }
            return ans;
        }

        double min(double... v) {
            double ans = v[0];
            for (int i=1; i<v.length; i++) {
                ans = Math.min(ans, v[i]);
            }
            return ans;
        }

        int min(int... v) {
            int ans = v[0];
            for (int i=1; i<v.length; i++) {
                ans = Math.min(ans, v[i]);
            }
            return ans;
        }

        long max(long... v) {
            long ans = v[0];
            for (int i=1; i<v.length; i++) {
                ans = Math.max(ans, v[i]);
            }
            return ans;
        }

        double max(double... v) {
            double ans = v[0];
            for (int i=1; i<v.length; i++) {
                ans = Math.max(ans, v[i]);
            }
            return ans;
        }

        int max(int... v) {
            int ans = v[0];
            for (int i=1; i<v.length; i++) {
                ans = Math.max(ans, v[i]);
            }
            return ans;
        }

    }

}

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.util.StringTokenizer;

/**
 * Created by timur on 28.03.15.
 */

public class TaskD {
    boolean eof;
    BufferedReader br;
    StringTokenizer st;
    PrintWriter out;

    public static void main(String[] args) throws IOException {
        new TaskD().run();
    }

    public String nextToken() {
        while (st == null || !st.hasMoreTokens()) {
            try {
                st = new StringTokenizer(br.readLine());
            } catch (Exception e) {
                eof = true;
                return "-1";
            }
        }
        return st.nextToken();
    }

    public int nextInt() {
        return Integer.parseInt(nextToken());
    }

    public long nextLong() {
        return Long.parseLong(nextToken());
    }

    double nextDouble() {
        return Double.parseDouble(nextToken());
    }

    String nextLine() throws IOException {
        return br.readLine();
    }

    void run() throws IOException {
        InputStream input = System.in;
        PrintStream output = System.out;
        try {
            File f = new File("a.in");
            if (f.exists() && f.canRead()) {
                input = new FileInputStream(f);
                output = new PrintStream("a.out");
            }
        } catch (Throwable e) {
        }
        br = new BufferedReader(new InputStreamReader(input));
        out = new PrintWriter(output);
        solve();
        br.close();
        out.close();
    }

    long gcd(long a, long b) {
        if (b == 0)
            return a;
        else
            return gcd(b, a % b);
    }

    class Pair implements Comparable<Pair> {
        int move, change, num;

        public Pair(int move, int change, int num) {
            this.move = move;
            this.change = change;
            this.num = num;

        }

        @Override
        public int compareTo(Pair o) {
            int r = move - o.move;
            if (r == 0)
                r = this.change - o.change;
            return r;
        }

        public Pair up() {
            return new Pair(move + 1, change + 1, num);
        }
    }

    Pair min(Pair a, Pair b) {
        if (a == null)
            return b;
        if (b == null)
            return a;
        if (b.compareTo(a) < 0)
            return b;
        return a;
    }

    Pair numin(Pair a, Pair b) {
        if (a == null)
            return b;
        if (b == null)
            return a;
        if (a.num > b.num)
            return a;
        if (a.num < b.num)
            return b;
        return min(a, b);
    }

    class map {
        Pair[][] m;

        public map() {
            m = new Pair[n][n];
        }
    }

    int n, k;

    void solve() {
        n = nextInt();
        k = n * n;
        int[] x = new int[k + 1];
        int[] y = new int[k + 1];
        int u;
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                u = nextInt() - 1;
                x[u] = i;
                y[u] = j;
            }
        }
        int inf = k * 2 + 5;
        map[] h = new map[inf];
        map[] s = new map[inf];
        map[] l = new map[inf];
        for (int i = 0; i < inf; i++) {
            h[i] = new map();
            s[i] = new map();
            l[i] = new map();
        }
        h[0].m[x[0]][y[0]] = new Pair(0, 0, 1);
        l[0].m[x[0]][y[0]] = new Pair(0, 0, 1);
        s[0].m[x[0]][y[0]] = new Pair(0, 0, 1);

        int[] dxh = {-2, -2, -1, 1, 2, 2, 1, -1};
        int[] dyh = {-1, 1, 2, 2, 1, -1, -2, -2};

        int[] dxs = {-1, -1, 1, 1};
        int[] dys = {-1, 1, 1, -1};

        int[] dxl = {-1, 0, 1, 0};
        int[] dyl = {0, 1, 0, -1};

        int nx, ny;
        Pair np;
        Pair cp;

        Pair ans = null;
        for (int i = 0; i < inf - 3; i++) {
            for (int xx = 0; xx < n; xx++) {
                for (int yy = 0; yy < n; yy++) {
                    cp = h[i].m[xx][yy];
                    if (cp != null) {
                        for (int j = 0; j < 8; j++) {
                            nx = xx + dxh[j];
                            ny = yy + dyh[j];
                            if (nx >= 0 && ny >= 0 && nx < n && ny < n) {
                                np = new Pair(cp.move + 1, cp.change, cp.num);
                                if (cp.num < k && nx == x[cp.num] && ny == y[cp.num]) {
                                    np = new Pair(cp.move + 1, cp.change, cp.num + 1);
                                }
                                h[i + 1].m[nx][ny] = numin(h[i + 1].m[nx][ny], np);
                                s[i + 2].m[nx][ny] = numin(s[i + 1].m[nx][ny], np.up());
                                l[i + 2].m[nx][ny] = numin(l[i + 1].m[nx][ny], np.up());
                                ans = numin(ans, np);
                            }
                        }
                    }
                    cp = s[i].m[xx][yy];
                    if (cp != null) {
                        for (int j = 0; j < 4; j++) {
                            for (int mul = 1; mul < n + 1; mul++) {
                                nx = xx + dxs[j] * mul;
                                ny = yy + dys[j] * mul;
                                if (nx >= 0 && ny >= 0 && nx < n && ny < n) {
                                    np = new Pair(cp.move + 1, cp.change, cp.num);
                                    if (cp.num < k && nx == x[cp.num] && ny == y[cp.num]) {
                                        np = new Pair(cp.move + 1, cp.change, cp.num + 1);
                                    }
                                    s[i + 1].m[nx][ny] = numin(s[i + 1].m[nx][ny], np);
                                    h[i + 2].m[nx][ny] = numin(h[i + 1].m[nx][ny], np.up());
                                    l[i + 2].m[nx][ny] = numin(l[i + 1].m[nx][ny], np.up());
                                    ans = numin(ans, np);
                                } else {
                                    break;
                                }
                            }
                        }
                    }
                    cp = l[i].m[xx][yy];
                    if (cp != null) {
                        for (int j = 0; j < 4; j++) {
                            for (int mul = 1; mul < n + 1; mul++) {
                                nx = xx + dxl[j] * mul;
                                ny = yy + dyl[j] * mul;
                                if (nx >= 0 && ny >= 0 && nx < n && ny < n) {
                                    np = new Pair(cp.move + 1, cp.change, cp.num);
                                    if (cp.num < k && nx == x[cp.num] && ny == y[cp.num]) {
                                        np = new Pair(cp.move + 1, cp.change, cp.num + 1);
                                    }
                                    l[i + 1].m[nx][ny] = numin(l[i + 1].m[nx][ny], np);
                                    s[i + 2].m[nx][ny] = numin(s[i + 1].m[nx][ny], np.up());
                                    h[i + 2].m[nx][ny] = numin(h[i + 1].m[nx][ny], np.up());
                                    ans = numin(ans, np);
                                } else {
                                    break;
                                }
                            }
                        }
                    }
                }
            }
        }

        out.print(ans.move + " " + ans.change);
        /*

        for (int i = 1; i < k; i++) {
            if (x[i] == x[i - 1] || y[i] == y[i - 1]) {
                l[i] = l[i - 1] + 1;
            } else {
                l[i] = l[i - 1] + 2;
            }
            zl[i] = zl[i - 1];
            s[i] = l[i] + 1;
            zs[i] = zl[i - 1] + 1;
            h[i] = l[i] + 1;
            zh[i] = zl[i - 1] + 1;
            if ((x[i] + y[i]) % 2 == (x[i - 1] + y[i - 1]) % 2) {
                if ((x[i] - y[i] == x[i - 1] - y[i - 1]) ||
                    (x[i] + y[i] == x[i - 1] + y[i - 1])) {
                    s[i] = s[i - 1] + 1;
                } else {
                    s[i] = s[i - 1] + 2;
                    if (s[i] + 1 < l[i] || (s[i] + 1 == l[i] && zs[i] + 1 < zl[i])) {
                        l[i] = s[i] + 1;
                        zl[i] = zs[i] + 1;
                    }
                    if (s[i] + 1 < h[i] || (s[i] + 1 == h[i] && zs[i] + 1 < zh[i])) {
                        h[i] = s[i] + 1;
                        zh[i] = zs[i] + 1;
                    }
                }
            }
            int mv = inf;
            Queue<Integer> xx = new ArrayDeque<>();
            Queue<Integer> yy = new ArrayDeque<>();
            Queue<Integer> c = new ArrayDeque<>();
            xx.add(x[i - 1]);
            yy.add(y[i - 1]);
            c.add(0);
            boolean[][] vis = new boolean[n][n];
            vis[x[i - 1]][y[i - 1]] = true;
            while (!xx.isEmpty()) {
                cx = xx.poll();
                cy = yy.poll();
                cc = c.poll();
                for (int j = 0; j < 8; j++) {
                    nx = cx + dx[j];
                    ny = cy + dy[j];
                    if (nx >= 0 && ny >= 0 && nx < n && ny < n) {
                        if (nx == x[i] && ny == y[i]) {
                            mv = cc + 1;
                            xx.clear();
                            break;
                        }
                        if (!vis[nx][ny]) {
                            xx.add(nx);
                            yy.add(ny);
                            c.add(cc + 1);
                            vis[nx][ny] = true;
                        }
                    }
                }
            }
            if (h[i - 1] + mv < h[i] || (h[i - 1] + mv == h[i] && zh[i] > zh[i - 1])) {
                h[i] = h[i - 1] + mv;
                zh[i] = zh[ i - 1];
            }
            if (h[i] + 1 < l[i] || (h[i] + 1 == l[i] && zh[i] + 1 < zl[i])) {
                l[i] = h[i] + 1;
                zl[i] = zh[i] + 1;
            }
            if (h[i] + 1 < s[i] || (h[i] + 1 == s[i] && zh[i] + 1 < zs[i])) {
                s[i] = h[i] + 1;
                zs[i] = zh[i] + 1;
            }
        }
        Pair lll = new Pair(l[k - 1], zl[k - 1]);
        Pair hhh = new Pair(h[k - 1], zh[k - 1]);
        Pair sss = new Pair(s[k - 1], zs[k - 1]);
        Pair res = lll;
        if (res.compareTo(hhh) > 0) {
            res = hhh;
        }
        if (res.compareTo(sss) > 0) {
            res = sss;
        }
        out.print(res.l + " " + res.change);
        */
    }

}

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.Queue;
import java.util.StringTokenizer;

/**
 * Created by timur on 28.03.15.
 */

public class TaskD {
    boolean eof;
    BufferedReader br;
    StringTokenizer st;
    PrintWriter out;

    public static void main(String[] args) throws IOException {
        new TaskD().run();
    }

    public String nextToken() {
        while (st == null || !st.hasMoreTokens()) {
            try {
                st = new StringTokenizer(br.readLine());
            } catch (Exception e) {
                eof = true;
                return "-1";
            }
        }
        return st.nextToken();
    }

    public int nextInt() {
        return Integer.parseInt(nextToken());
    }

    public long nextLong() {
        return Long.parseLong(nextToken());
    }

    double nextDouble() {
        return Double.parseDouble(nextToken());
    }

    String nextLine() throws IOException {
        return br.readLine();
    }

    void run() throws IOException {
        InputStream input = System.in;
        PrintStream output = System.out;
        try {
            File f = new File("a.in");
            if (f.exists() && f.canRead()) {
                input = new FileInputStream(f);
                output = new PrintStream("a.out");
            }
        } catch (Throwable e) {
        }
        br = new BufferedReader(new InputStreamReader(input));
        out = new PrintWriter(output);
        solve();
        br.close();
        out.close();
    }

    long gcd(long a, long b) {
        if (b == 0)
            return a;
        else
            return gcd(b, a % b);
    }

    class Pair implements Comparable<Pair> {
        int l, r;

        public Pair(int l, int r) {
            this.l = l;
            this.r = r;
        }

        @Override
        public int compareTo(Pair o) {
            int r = l - o.l;
            if (r == 0)
                r = this.r - o.r;
            return r;
        }
    }

    void solve() {
        int n = nextInt();
        int k = n * n;
        int[] x = new int[k];
        int[] y = new int[k];
        int u;
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                u = nextInt() - 1;
                x[u] = i;
                y[u] = j;
            }
        }
        int[] h = new int[k],
            s = new int[k],
            l = new int[k];
        int[] zh = new int[k],
            zs = new int[k],
            zl = new int[k];
        int inf = k * 2;
        for (int i = 0; i < k; i++) {
            h[i] = inf;
            s[i] = inf;
            l[i] = inf;
        }
        h[0] = 0;
        s[0] = 0;
        l[0] = 0;
        int[] dx = {-2, -2, -1, 1, 2, 2, 1, -1};
        int[] dy = {-1, 1, 2, 2, 1, -1, -2, -2};
        int cx, cy, cc, nx, ny;
        for (int i = 1; i < k; i++) {
            if (x[i] == x[i - 1] || y[i] == y[i - 1]) {
                l[i] = l[i - 1] + 1;
            } else {
                l[i] = l[i - 1] + 2;
            }
            zl[i] = zl[i - 1];
            s[i] = l[i] + 1;
            zs[i] = zl[i - 1] + 1;
            h[i] = l[i] + 1;
            zh[i] = zl[i - 1] + 1;
            if ((x[i] + y[i]) % 2 == (x[i - 1] + y[i - 1]) % 2) {
                if ((x[i] - y[i] == x[i - 1] - y[i - 1]) ||
                    (x[i] + y[i] == x[i - 1] + y[i - 1])) {
                    s[i] = s[i - 1] + 1;
                } else {
                    s[i] = s[i - 1] + 2;
                    if (s[i] + 1 < l[i] || (s[i] + 1 == l[i] && zs[i] + 1 < zl[i])) {
                        l[i] = s[i] + 1;
                        zl[i] = zs[i] + 1;
                    }
                    if (s[i] + 1 < h[i] || (s[i] + 1 == h[i] && zs[i] + 1 < zh[i])) {
                        h[i] = s[i] + 1;
                        zh[i] = zs[i] + 1;
                    }
                }
            }
            int mv = inf;
            Queue<Integer> xx = new ArrayDeque<>();
            Queue<Integer> yy = new ArrayDeque<>();
            Queue<Integer> c = new ArrayDeque<>();
            xx.add(x[i - 1]);
            yy.add(y[i - 1]);
            c.add(0);
            boolean[][] vis = new boolean[n][n];
            vis[x[i - 1]][y[i - 1]] = true;
            while (!xx.isEmpty()) {
                cx = xx.poll();
                cy = yy.poll();
                cc = c.poll();
                for (int j = 0; j < 8; j++) {
                    nx = cx + dx[j];
                    ny = cy + dy[j];
                    if (nx >= 0 && ny >= 0 && nx < n && ny < n) {
                        if (nx == x[i] && ny == y[i]) {
                            mv = cc + 1;
                            xx.clear();
                            break;
                        }
                        if (!vis[nx][ny]) {
                            xx.add(nx);
                            yy.add(ny);
                            c.add(cc + 1);
                            vis[nx][ny] = true;
                        }
                    }
                }
            }
            if (h[i - 1] + mv < h[i] || (h[i - 1] + mv == h[i] && zh[i] > zh[i - 1])) {
                h[i] = h[i - 1] + mv;
                zh[i] = zh[ i - 1];
            }
            if (h[i] + 1 < l[i] || (h[i] + 1 == l[i] && zh[i] + 1 < zl[i])) {
                l[i] = h[i] + 1;
                zl[i] = zh[i] + 1;
            }
            if (h[i] + 1 < s[i] || (h[i] + 1 == s[i] && zh[i] + 1 < zs[i])) {
                s[i] = h[i] + 1;
                zs[i] = zh[i] + 1;
            }
        }
        Pair lll = new Pair(l[k - 1], zl[k - 1]);
        Pair hhh = new Pair(h[k - 1], zh[k - 1]);
        Pair sss = new Pair(s[k - 1], zs[k - 1]);
        Pair res = lll;
        if (res.compareTo(hhh) > 0) {
            res = hhh;
        }
        if (res.compareTo(sss) > 0) {
            res = sss;
        }
        out.print(res.l + " " + res.r);
    }

}

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.util.StringTokenizer;

/**
 * Created by timur on 28.03.15.
 */

public class TaskD {
    boolean eof;
    BufferedReader br;
    StringTokenizer st;
    PrintWriter out;

    public static void main(String[] args) throws IOException {
        new TaskD().run();
    }

    public String nextToken() {
        while (st == null || !st.hasMoreTokens()) {
            try {
                st = new StringTokenizer(br.readLine());
            } catch (Exception e) {
                eof = true;
                return "-1";
            }
        }
        return st.nextToken();
    }

    public int nextInt() {
        return Integer.parseInt(nextToken());
    }

    public long nextLong() {
        return Long.parseLong(nextToken());
    }

    double nextDouble() {
        return Double.parseDouble(nextToken());
    }

    String nextLine() throws IOException {
        return br.readLine();
    }

    void run() throws IOException {
        InputStream input = System.in;
        PrintStream output = System.out;
        try {
            File f = new File("a.in");
            if (f.exists() && f.canRead()) {
                input = new FileInputStream(f);
                output = new PrintStream("a.out");
            }
        } catch (Throwable e) {
        }
        br = new BufferedReader(new InputStreamReader(input));
        out = new PrintWriter(output);
        solve();
        br.close();
        out.close();
    }

    long gcd(long a, long b) {
        if (b == 0)
            return a;
        else
            return gcd(b, a % b);
    }

    class Pair implements Comparable<Pair> {
        int move, change, num;

        public Pair(int move, int change, int num) {
            this.move = move;
            this.change = change;
            this.num = num;

        }

        @Override
        public int compareTo(Pair o) {
            int r = move - o.move;
            if (r == 0)
                r = this.change - o.change;
            return r;
        }

        public Pair up() {
            return new Pair(move, change + 1, num);
        }
    }

    Pair min(Pair a, Pair b) {
        if (a == null)
            return b;
        if (b == null)
            return a;
        if (b.compareTo(a) < 0)
            return b;
        return a;
    }

    Pair numin(Pair a, Pair b) {
        if (a == null)
            return b;
        if (b == null)
            return a;
        if (a.num > b.num)
            return a;
        if (a.num < b.num)
            return b;
        return min(a, b);
    }

    class map {
        Pair[][] m;

        public map() {
            m = new Pair[n][n];
        }
    }

    int n, k;

    void solve() {
        n = nextInt();
        k = n * n;
        int[] x = new int[k + 1];
        int[] y = new int[k + 1];
        int u;
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                u = nextInt() - 1;
                x[u] = i;
                y[u] = j;
            }
        }
        int inf = k * 2 + 5;
        map[] h = new map[inf];
        map[] s = new map[inf];
        map[] l = new map[inf];
        for (int i = 0; i < inf; i++) {
            h[i] = new map();
            s[i] = new map();
            l[i] = new map();
        }
        h[0].m[x[0]][y[0]] = new Pair(0, 0, 0);
        l[0].m[x[0]][y[0]] = new Pair(0, 0, 0);
        s[0].m[x[0]][y[0]] = new Pair(0, 0, 0);

        int[] dxh = {-2, -2, -1, 1, 2, 2, 1, -1};
        int[] dyh = {-1, 1, 2, 2, 1, -1, -2, -2};

        int[] dxs = {-1, -1, 1, 1};
        int[] dys = {-1, 1, 1, -1};

        int[] dxl = {-1, 0, 1, 0};
        int[] dyl = {0, 1, 0, -1};

        int nx, ny;
        Pair np;
        Pair cp;

        Pair ans = null;
        for (int i = 0; i < inf - 5; i++) {
            for (int xx = 0; xx < n; xx++) {
                for (int yy = 0; yy < n; yy++) {
                    cp = h[i].m[xx][yy];
                    if (cp != null) {
                        for (int j = 0; j < 8; j++) {
                            nx = xx + dxh[j];
                            ny = yy + dyh[j];
                            if (nx >= 0 && ny >= 0 && nx < n && ny < n) {
                                np = new Pair(cp.move + 1, cp.change, cp.num);
                                if (cp.num < k - 1 && nx == x[cp.num] && ny == y[cp.num]) {
                                    np = new Pair(cp.move + 1, cp.change, cp.num + 1);
                                }
                                h[i + 1].m[nx][ny] = numin(h[i + 1].m[nx][ny], np);
                                s[i + 1].m[nx][ny] = numin(s[i + 1].m[nx][ny], np.up());
                                l[i + 1].m[nx][ny] = numin(l[i + 1].m[nx][ny], np.up());
                                ans = numin(ans, np);
                            }
                        }
                    }
                    cp = s[i].m[xx][yy];
                    if (cp != null) {
                        for (int j = 0; j < 4; j++) {
                            for (int mul = 1; mul < n + 1; mul++) {
                                nx = xx + dxs[j] * mul;
                                ny = yy + dys[j] * mul;
                                if (nx >= 0 && ny >= 0 && nx < n && ny < n) {
                                    np = new Pair(cp.move + mul, cp.change, cp.num);
                                    if (cp.num < k - 1 && nx == x[cp.num] && ny == y[cp.num]) {
                                        np = new Pair(cp.move + mul, cp.change, cp.num + 1);
                                    }
                                    s[i + 1].m[nx][ny] = numin(s[i + 1].m[nx][ny], np);
                                    h[i + 1].m[nx][ny] = numin(h[i + 1].m[nx][ny], np.up());
                                    l[i + 1].m[nx][ny] = numin(l[i + 1].m[nx][ny], np.up());
                                    ans = numin(ans, np);
                                } else {
                                    break;
                                }
                            }
                        }
                    }
                    cp = l[i].m[xx][yy];
                    if (cp != null) {
                        for (int j = 0; j < 4; j++) {
                            for (int mul = 1; mul < n + 1; mul++) {
                                nx = xx + dxl[j] * mul;
                                ny = yy + dyl[j] * mul;
                                if (nx >= 0 && ny >= 0 && nx < n && ny < n) {
                                    np = new Pair(cp.move + mul, cp.change, cp.num);
                                    if (cp.num < k - 1 && nx == x[cp.num] && ny == y[cp.num]) {
                                        np = new Pair(cp.move + mul, cp.change, cp.num + 1);
                                    }
                                    l[i + 1].m[nx][ny] = numin(l[i + 1].m[nx][ny], np);
                                    s[i + 1].m[nx][ny] = numin(s[i + 1].m[nx][ny], np.up());
                                    h[i + 1].m[nx][ny] = numin(h[i + 1].m[nx][ny], np.up());
                                    ans = numin(ans, np);
                                } else {
                                    break;
                                }
                            }
                        }
                    }
                }
            }
        }

        out.print(ans.move + " " + (ans.change - 1));// + " " + ans.num);
        /*

        for (int i = 1; i < k; i++) {
            if (x[i] == x[i - 1] || y[i] == y[i - 1]) {
                l[i] = l[i - 1] + 1;
            } else {
                l[i] = l[i - 1] + 2;
            }
            zl[i] = zl[i - 1];
            s[i] = l[i] + 1;
            zs[i] = zl[i - 1] + 1;
            h[i] = l[i] + 1;
            zh[i] = zl[i - 1] + 1;
            if ((x[i] + y[i]) % 2 == (x[i - 1] + y[i - 1]) % 2) {
                if ((x[i] - y[i] == x[i - 1] - y[i - 1]) ||
                    (x[i] + y[i] == x[i - 1] + y[i - 1])) {
                    s[i] = s[i - 1] + 1;
                } else {
                    s[i] = s[i - 1] + 2;
                    if (s[i] + 1 < l[i] || (s[i] + 1 == l[i] && zs[i] + 1 < zl[i])) {
                        l[i] = s[i] + 1;
                        zl[i] = zs[i] + 1;
                    }
                    if (s[i] + 1 < h[i] || (s[i] + 1 == h[i] && zs[i] + 1 < zh[i])) {
                        h[i] = s[i] + 1;
                        zh[i] = zs[i] + 1;
                    }
                }
            }
            int mv = inf;
            Queue<Integer> xx = new ArrayDeque<>();
            Queue<Integer> yy = new ArrayDeque<>();
            Queue<Integer> c = new ArrayDeque<>();
            xx.add(x[i - 1]);
            yy.add(y[i - 1]);
            c.add(0);
            boolean[][] vis = new boolean[n][n];
            vis[x[i - 1]][y[i - 1]] = true;
            while (!xx.isEmpty()) {
                cx = xx.poll();
                cy = yy.poll();
                cc = c.poll();
                for (int j = 0; j < 8; j++) {
                    nx = cx + dx[j];
                    ny = cy + dy[j];
                    if (nx >= 0 && ny >= 0 && nx < n && ny < n) {
                        if (nx == x[i] && ny == y[i]) {
                            mv = cc + 1;
                            xx.clear();
                            break;
                        }
                        if (!vis[nx][ny]) {
                            xx.add(nx);
                            yy.add(ny);
                            c.add(cc + 1);
                            vis[nx][ny] = true;
                        }
                    }
                }
            }
            if (h[i - 1] + mv < h[i] || (h[i - 1] + mv == h[i] && zh[i] > zh[i - 1])) {
                h[i] = h[i - 1] + mv;
                zh[i] = zh[ i - 1];
            }
            if (h[i] + 1 < l[i] || (h[i] + 1 == l[i] && zh[i] + 1 < zl[i])) {
                l[i] = h[i] + 1;
                zl[i] = zh[i] + 1;
            }
            if (h[i] + 1 < s[i] || (h[i] + 1 == s[i] && zh[i] + 1 < zs[i])) {
                s[i] = h[i] + 1;
                zs[i] = zh[i] + 1;
            }
        }
        Pair lll = new Pair(l[k - 1], zl[k - 1]);
        Pair hhh = new Pair(h[k - 1], zh[k - 1]);
        Pair sss = new Pair(s[k - 1], zs[k - 1]);
        Pair res = lll;
        if (res.compareTo(hhh) > 0) {
            res = hhh;
        }
        if (res.compareTo(sss) > 0) {
            res = sss;
        }
        out.print(res.l + " " + res.change);
        */
    }

}

import java.util.*;
import java.lang.*;
import java.math.*;
import java.io.*;
import static java.lang.Math.*;

/* spar5h */

public class cf2{

	static int[][] dp, dp2, d, d2;
	static int[] ai, aj;

	static void update(int s_i, int f_i, int s_j, int f_j, int val, int c_i, int c_j) {

		for(int i = s_i; i <= f_i; i++) {

			for(int j = s_j; j <= f_j; j++) {

				int rpl = 0;

				if(i != c_i)
					rpl++;

				if(j != c_j)
					rpl++;

				if(d[i][j] > val + rpl) {
					d[i][j] = val + rpl; d2[i][j] = rpl;
				}
				else if(d[i][j] == val + rpl)
					d2[i][j] = min(rpl, d2[i][j]);

			}
		}
	}

	static boolean rook(int si, int sj, int ti, int tj) {

		if(si == ti || sj == tj)
			return true;

		return false;
	}

	static boolean bishop(int si, int sj, int ti, int tj) {

		if(abs(si - ti) == abs(sj - tj))
			return true;

		return false;
	}

	static boolean knight(int si, int sj, int ti, int tj) {

		if((abs(si - ti) == 1 && abs(sj - tj) == 2) || (abs(si - ti) == 2 && abs(sj - tj) == 1))
			return true;

		return false;
	}

	static void recur(int x, int n) {

		//rook: 0, bishop; 1, knight: 2

		if(x + 1 < n * n - 1)
			recur(x + 1, n);

		int si = ai[x], sj = aj[x], ti = ai[x + 1], tj = aj[x + 1];

		d = new int[3][3]; d2 = new int[3][3];

		for(int i = 0; i < 3; i++)
			for(int j = 0; j < 3; j++)
				d[i][j] = Integer.MAX_VALUE;

		update(0, 2, 0, 2, 2, 0, 0);

		//1
		if(rook(si, sj, ti, tj))
			update(0, 2, 0, 2, 1, 0, 0);

		if(bishop(si, sj, ti, tj))
			update(0, 2, 0, 2, 1, 1, 1);

		if(knight(si, sj, ti, tj))
			update(0, 2, 0, 2, 1, 2, 2);

		//2
		for(int xi = 0; xi < n; xi++) {

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

				if(bishop(si, sj, xi, xj) && bishop(xi, xj, ti, tj))
					update(1, 2, 1, 2, 2, 1, 1);

				if(knight(si, sj, xi, xj) && knight(xi, xj, ti, tj))
					update(1, 2, 1, 2, 2, 2, 2);

				if(bishop(si, sj, xi, xj) && knight(xi, xj, ti, tj))
					update(1, 1, 2, 2, 3, 1, 2);

				if(knight(si, sj, xi, xj) && bishop(xi, xj, ti, tj))
					update(2, 2, 1, 1, 3, 2, 1);
			}
		}

		//3
		for(int xi = 0; xi < n; xi++) {

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

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

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

						if(bishop(si, sj, xi, xj) && bishop(xi, xj, yi, yj) && bishop(yi, yj, ti, tj))
							update(1, 1, 1, 1, 3, 1, 1);

						if(knight(si, sj, xi, xj) && knight(xi, xj, yi, yj) && knight(yi, yj, ti, tj))
							update(2, 2, 2, 2, 3, 2, 2);
					}
				}
			}
		}

		for(int i = 0; i < 3; i++)
			dp[x][i] = Integer.MAX_VALUE;

		for(int i = 0; i < 3; i++) {
			for(int j = 0; j < 3; j++) {

				if(dp[x][i] > d[i][j] + dp[x + 1][j]) {
					dp[x][i] = d[i][j] + dp[x + 1][j]; dp2[x][i] = d2[i][j] + dp2[x + 1][j];
				}
				else if(dp[x][i] == d[i][j] + dp[x + 1][j])
					dp2[x][i] = min(d2[i][j] + dp2[x + 1][j], dp2[x][i]);
			}
		}
	}

	public static void main(String[] args) {

		InputReader s = new InputReader(System.in);
		PrintWriter w = new PrintWriter(System.out);

		int n = s.nextInt();

		ai = new int[n * n];
		aj = new int[n * n];

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

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

				int x = s.nextInt() - 1;
				ai[x] = i; aj[x] = j;
			}
		}

		dp = new int[n * n][3];
		dp2 = new int[n * n][3];

		recur(0, n);

		int v1 = Integer.MAX_VALUE, v2 = 0;

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

			if(dp[0][i] < v1) {
				v1 = dp[0][i]; v2 = dp2[0][i];
			}
			else if(dp[0][i] == v1)
				v2 = min(dp2[0][i], v2);
		}

		w.println(v1 + " " + v2);

		w.close();
	}

	static class InputReader {

		private InputStream stream;
		private byte[] buf = new byte[1024];
		private int curChar;
		private int numChars;
		private SpaceCharFilter filter;

		public InputReader(InputStream stream)
		{
			this.stream = stream;
		}

		public int read()
		{
			if (numChars==-1)
				throw new InputMismatchException();

			if (curChar >= numChars)
			{
				curChar = 0;
				try
				{
					numChars = stream.read(buf);
				}
				catch (IOException e)
				{
					throw new InputMismatchException();
				}

				if(numChars <= 0)
					return -1;
			}
			return buf[curChar++];
		}

		public String nextLine()
		{
			BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
			String str = "";
            try
            {
                str = br.readLine();
            }
            catch (IOException e)
            {
                e.printStackTrace();
            }
            return str;
		}
		public int nextInt()
		{
			int c = read();

			while(isSpaceChar(c))
				c = read();

			int sgn = 1;

			if (c == '-')
			{
				sgn = -1;
				c = read();
			}

			int res = 0;
			do
			{
				if(c<'0'||c>'9')
					throw new InputMismatchException();
				res *= 10;
				res += c - '0';
				c = read();
			}
			while (!isSpaceChar(c));

			return res * sgn;
		}

		public long nextLong()
		{
			int c = read();
			while (isSpaceChar(c))
				c = read();
			int sgn = 1;
			if (c == '-')
			{
				sgn = -1;
				c = read();
			}
			long res = 0;

			do
			{
				if (c < '0' || c > '9')
					throw new InputMismatchException();
				res *= 10;
				res += c - '0';
				c = read();
			}
			while (!isSpaceChar(c));
				return res * sgn;
		}

		public double nextDouble()
		{
			int c = read();
			while (isSpaceChar(c))
				c = read();
			int sgn = 1;
			if (c == '-')
			{
				sgn = -1;
				c = read();
			}
			double res = 0;
			while (!isSpaceChar(c) && c != '.')
			{
				if (c == 'e' || c == 'E')
					return res * Math.pow(10, nextInt());
				if (c < '0' || c > '9')
					throw new InputMismatchException();
				res *= 10;
				res += c - '0';
				c = read();
			}
			if (c == '.')
			{
				c = read();
				double m = 1;
				while (!isSpaceChar(c))
				{
					if (c == 'e' || c == 'E')
						return res * Math.pow(10, nextInt());
					if (c < '0' || c > '9')
						throw new InputMismatchException();
					m /= 10;
					res += (c - '0') * m;
					c = read();
				}
			}
			return res * sgn;
		}

		public String readString()
		{
			int c = read();
			while (isSpaceChar(c))
				c = read();
			StringBuilder res = new StringBuilder();
			do
			{
				res.appendCodePoint(c);
				c = read();
			}
			while (!isSpaceChar(c));

			return res.toString();
		}

		public boolean isSpaceChar(int c)
		{
			if (filter != null)
				return filter.isSpaceChar(c);
			return c == ' ' || c == '\n' || c == '\r' || c == '\t' || c == -1;
		}

		public String next()
		{
			return readString();
		}

		public interface SpaceCharFilter
		{
			public boolean isSpaceChar(int ch);
		}
	}

}

import java.io.*;
import java.math.*;
import java.util.*;

public class ThreePieces {
    private static final int INF = 1234567890;

    public static void solve(FastIO io) {
        int N = io.nextInt();

        int r0 = -1, c0 = -1;
        int[][] A = new int[N][];
        for (int i = 0; i < N; ++i) {
            A[i] = io.nextIntArray(N);

            for (int j = 0; j < N; ++j) {
                if (A[i][j] == 1) {
                    r0 = i;
                    c0 = j;
                }
            }
        }

        LocList mb = new LocList();
        for (int i = 1; i <= N; ++i) {
            mb.add(new Loc(i, i));
            mb.add(new Loc(i, -i));
            mb.add(new Loc(-i, i));
            mb.add(new Loc(-i, -i));
        }

        LocList mk = new LocList();
        mk.add(new Loc(-2, 1));
        mk.add(new Loc(-1, 2));
        mk.add(new Loc(1, 2));
        mk.add(new Loc(2, 1));
        mk.add(new Loc(2, -1));
        mk.add(new Loc(1, -2));
        mk.add(new Loc(-1, -2));
        mk.add(new Loc(-2, -1));

        LocList mr = new LocList();
        for (int i = 1; i <= N; ++i) {
            mr.add(new Loc(0, i));
            mr.add(new Loc(0, -1));
            mr.add(new Loc(i, 0));
            mr.add(new Loc(-i, 0));
        }

        LocList[] moves = {mb, mk, mr};

        int N2 = N * N;
        int[][][][] dp = megaInit(N);
        dp[r0][c0][1][0] = dp[r0][c0][1][1] = dp[r0][c0][1][2] = 0;

        for (int t = 0;; ++t) {
            int[][][][] next = megaInit(N);
            int bestFinish = INF;
            for (int r = 0; r < N; ++r) {
                for (int c = 0; c < N; ++c) {
                    for (int i = 1; i < N2; ++i) {
                        int ni = i;
                        if (A[r][c] == i) {
                            ++ni;
                        }
                        for (int p = 0; p < 3; ++p) {
                            for (int np = 0; np < 3; ++np) {
                                next[r][c][ni][np] = Math.min(next[r][c][ni][np], 1 + dp[r][c][i][p]);
                            }
                            if (dp[r][c][i][p] < INF) {
                                for (Loc mv : moves[p]) {
                                    int nr = r + mv.R;
                                    int nc = c + mv.C;
                                    if (nr >= 0 && nr < N && nc >= 0 && nc < N) {
                                        next[nr][nc][ni][p] = Math.min(next[nr][nc][ni][p], dp[r][c][i][p]);
                                    }
                                }
                            }
                        }
                    }
                    for (int p = 0; p < 3; ++p) {
                        bestFinish = Math.min(bestFinish, dp[r][c][N2][p]);
                    }
                }
            }
            if (bestFinish < INF) {
                io.printf("%d %d\n", t, bestFinish);
                return;
            }
            dp = next;
        }
    }

    private static int[][][][] megaInit(int N) {
        final int N2 = N * N;
        int[][][][] arr = new int[N][N][N2 + 1][3];
        for (int r = 0; r < N; ++r) {
            for (int c = 0; c < N; ++c) {
                for (int i = 0; i <= N2; ++i) {
                    Arrays.fill(arr[r][c][i], INF);
                }
            }
        }
        return arr;
    }

    private static class LocList extends ArrayList<Loc> {
    }

    private static class Loc {
        public int R, C;

        public Loc(int r, int c) {
            this.R = r;
            this.C = c;
        }
    }

    public static class FastIO {
        private InputStream reader;
        private PrintWriter writer;

        private byte[] buf = new byte[1024];
        private int curChar;
        private int numChars;

        public FastIO(InputStream r, OutputStream w) {
            reader = r;
            writer = new PrintWriter(new BufferedWriter(new OutputStreamWriter(w)));
        }

        public int read() {
            if (numChars == -1)
                throw new InputMismatchException();
            if (curChar >= numChars) {
                curChar = 0;
                try {
                    numChars = reader.read(buf);
                } catch (IOException e) {
                    throw new InputMismatchException();
                }
                if (numChars <= 0)
                    return -1;
            }
            return buf[curChar++];
        }

        public String nextLine() {
            int c = read();
            while (isSpaceChar(c))
                c = read();
            StringBuilder res = new StringBuilder();
            do {
                res.appendCodePoint(c);
                c = read();
            } while (!isEndOfLine(c));
            return res.toString();
        }

        public String nextString() {
            int c = read();
            while (isSpaceChar(c))
                c = read();
            StringBuilder res = new StringBuilder();
            do {
                res.appendCodePoint(c);
                c = read();
            } while (!isSpaceChar(c));
            return res.toString();
        }

        public long nextLong() {
            int c = read();
            while (isSpaceChar(c))
                c = read();
            int sgn = 1;
            if (c == '-') {
                sgn = -1;
                c = read();
            }
            long res = 0;
            do {
                if (c < '0' || c > '9')
                    throw new InputMismatchException();
                res *= 10;
                res += c - '0';
                c = read();
            } while (!isSpaceChar(c));
            return res * sgn;
        }

        public int nextInt() {
            int c = read();
            while (isSpaceChar(c))
                c = read();
            int sgn = 1;
            if (c == '-') {
                sgn = -1;
                c = read();
            }
            int res = 0;
            do {
                if (c < '0' || c > '9')
                    throw new InputMismatchException();
                res *= 10;
                res += c - '0';
                c = read();
            } while (!isSpaceChar(c));
            return res * sgn;
        }

        public int[] nextIntArray(int n) {
            return nextIntArray(n, 0);
        }

        public int[] nextIntArray(int n, int off) {
            int[] arr = new int[n + off];
            for (int i = 0; i < n; i++) {
                arr[i + off] = nextInt();
            }
            return arr;
        }

        public long[] nextLongArray(int n) {
            return nextLongArray(n, 0);
        }

        public long[] nextLongArray(int n, int off) {
            long[] arr = new long[n + off];
            for (int i = 0; i < n; i++) {
                arr[i + off] = nextLong();
            }
            return arr;
        }

        private boolean isSpaceChar(int c) {
            return c == ' ' || c == '\n' || c == '\r' || c == '\t' || c == -1;
        }

        private boolean isEndOfLine(int c) {
            return c == '\n' || c == '\r' || c == -1;
        }

        public void print(Object... objects) {
            for (int i = 0; i < objects.length; i++) {
                if (i != 0) {
                    writer.print(' ');
                }
                writer.print(objects[i]);
            }
        }

        public void println(Object... objects) {
            print(objects);
            writer.println();
        }

        public void printArray(int[] arr) {
            for (int i = 0; i < arr.length; i++) {
                if (i != 0) {
                    writer.print(' ');
                }
                writer.print(arr[i]);
            }
        }

        public void printArray(long[] arr) {
            for (int i = 0; i < arr.length; i++) {
                if (i != 0) {
                    writer.print(' ');
                }
                writer.print(arr[i]);
            }
        }

        public void printlnArray(int[] arr) {
            printArray(arr);
            writer.println();
        }

        public void printlnArray(long[] arr) {
            printArray(arr);
            writer.println();
        }

        public void printf(String format, Object... args) {
            print(String.format(format, args));
        }

        public void flush() {
            writer.flush();
        }
    }

    public static void main(String[] args) {
        FastIO io = new FastIO(System.in, System.out);
        solve(io);
        io.flush();
    }
}

/*
If you want to aim high, aim high
Don't let that studying and grades consume you
Just live life young
******************************
What do you think? What do you think?
1st on Billboard, what do you think of it
Next is a Grammy, what do you think of it
However you think, I’m sorry, but shit, I have no fcking interest
*******************************
I'm standing on top of my Monopoly board
That means I'm on top of my game and it don't stop
til my hip don't hop anymore
https://www.a2oj.com/Ladder16.html
*******************************
Shining through the city with a little funk and soul
*******************************
300iq as writer = Sad!
*/
import java.util.*;
import java.io.*;
import java.math.*;

   public class x1065D
   {
      public static void main(String hi[]) throws Exception
      {
         BufferedReader infile = new BufferedReader(new InputStreamReader(System.in));
         StringTokenizer st = new StringTokenizer(infile.readLine());
         int N = Integer.parseInt(st.nextToken());
         int[][] grid = new int[N][N];
         for(int i=0; i < N; i++)
         {
            st = new StringTokenizer(infile.readLine());
            for(int a=0; a < N; a++)
               grid[i][a] = Integer.parseInt(st.nextToken());
         }
         int[] loc = new int[N*N+1];
         for(int r=0; r < N; r++)
            for(int c=0; c < N; c++)
               loc[grid[r][c]] = r*N+c;
         //knight bishop rook
         int[][][] dist = new int[N*N+1][3][N*N+1];
         int[][][] replace = new int[N*N+1][3][N*N+1];
         for(int i=0; i <= N*N; i++)
            for(int p=0; p < 3; p++)
            {
               Arrays.fill(dist[i][p], 420420420);
               Arrays.fill(replace[i][p], 420420420);
            }
         int start = loc[1];
         for(int p=0; p < 3; p++)
            dist[start][p][1] = replace[start][p][1] = 0;
         ArrayDeque<Integer> q = new ArrayDeque<Integer>();
         for(int p=0; p < 1; p++)
         {
            q.add(start);
            q.add(p);
            q.add(1);
         }
         while(q.size() > 0)
         {
            int tempo = q.poll();
            int piece = q.poll();
            int target = q.poll();
            int x = tempo/N;
            int y = tempo%N;
            //swap
            for(int p=0; p < 3; p++)
            {
               if(piece == p)
                  continue;
               if(replace[tempo][p][target] > replace[tempo][piece][target]+1 && dist[tempo][p][target] == dist[tempo][piece][target]+1)
               {
                  replace[tempo][p][target] = replace[tempo][piece][target]+1;
                  q.add(tempo);
                  q.add(p);
                  q.add(target);
               }
               else if(dist[tempo][p][target] > dist[tempo][piece][target]+1)
               {
                  replace[tempo][p][target] = replace[tempo][piece][target]+1;
                  dist[tempo][p][target] = dist[tempo][piece][target]+1;
                  q.add(tempo);
                  q.add(p);
                  q.add(target);
               }
            }
            //move
            if(piece == 0)
            {
               for(int dx=-2; dx <= 2; dx++)
                  for(int dy=-2; dy <= 2; dy++)
                  {
                     if(dx == 0 || dy == 0 || Math.abs(dx) == Math.abs(dy))
                        continue;
                     int r = x+dx;
                     int c = y+dy;
                     if(Math.min(r,c) < 0 || Math.max(r,c) >= N)
                        continue;
                     int id = target+(grid[r][c]-1 == target ? 1 : 0);
                     if(dist[r*N+c][piece][id] > dist[tempo][piece][target]+1)
                     {
                        replace[r*N+c][piece][id] = replace[tempo][piece][target];
                        dist[r*N+c][piece][id] = dist[tempo][piece][target]+1;
                        q.add(r*N+c);
                        q.add(piece);
                        q.add(id);
                     }
                     else if(dist[r*N+c][piece][id] == dist[tempo][piece][target]+1 && replace[r*N+c][piece][id] > replace[tempo][piece][target])
                     {
                        replace[r*N+c][piece][id] = replace[tempo][piece][target];
                        q.add(r*N+c);
                        q.add(piece);
                        q.add(id);
                     }
                  }
            }
            else if(piece == 1)
            {
               for(int a=1; a < N; a++)
                  for(int dx=-1; dx <= 1; dx+=2)
                     for(int dy=-1; dy <= 1; dy+=2)
                     {
                        int r = x+dx*a;
                        int c = y+dy*a;
                        if(Math.min(r,c) < 0 || Math.max(r,c) >= N)
                           continue;
                        int id = target+(grid[r][c]-1 == target ? 1 : 0);
                        if(dist[r*N+c][piece][id] > dist[tempo][piece][target]+1)
                        {
                           replace[r*N+c][piece][id] = replace[tempo][piece][target];
                           dist[r*N+c][piece][id] = dist[tempo][piece][target]+1;
                           q.add(r*N+c);
                           q.add(piece);
                           q.add(id);
                        }
                        else if(dist[r*N+c][piece][id] == dist[tempo][piece][target]+1 && replace[r*N+c][piece][id] > replace[tempo][piece][target])
                        {
                           replace[r*N+c][piece][id] = replace[tempo][piece][target];
                           q.add(r*N+c);
                           q.add(piece);
                           q.add(id);
                        }
                     }
            }
            else
            {
               for(int a=1; a < N; a++)
                  for(int dx=-1; dx <= 1; dx+=2)
                  {
                     int r = x+dx*a;
                     int c = y;
                     if(Math.min(r,c) < 0 || Math.max(r,c) >= N)
                        continue;
                     int id = target+(grid[r][c]-1 == target ? 1 : 0);
                     if(dist[r*N+c][piece][id] > dist[tempo][piece][target]+1)
                     {
                        replace[r*N+c][piece][id] = replace[tempo][piece][target];
                        dist[r*N+c][piece][id] = dist[tempo][piece][target]+1;
                        q.add(r*N+c);
                        q.add(piece);
                        q.add(id);
                     }
                     else if(dist[r*N+c][piece][id] == dist[tempo][piece][target]+1 && replace[r*N+c][piece][id] > replace[tempo][piece][target])
                     {
                        replace[r*N+c][piece][id] = replace[tempo][piece][target];
                        q.add(r*N+c);
                        q.add(piece);
                        q.add(id);
                     }
                  }
               for(int a=1; a < N; a++)
                  for(int dy=-1; dy <= 1; dy+=2)
                  {
                     int r = x;
                     int c = y+dy*a;
                     if(Math.min(r,c) < 0 || Math.max(r,c) >= N)
                        continue;
                     int id = target+(grid[r][c]-1 == target ? 1 : 0);
                     if(dist[r*N+c][piece][id] > dist[tempo][piece][target]+1)
                     {
                        replace[r*N+c][piece][id] = replace[tempo][piece][target];
                        dist[r*N+c][piece][id] = dist[tempo][piece][target]+1;
                        q.add(r*N+c);
                        q.add(piece);
                        q.add(id);
                     }
                     else if(dist[r*N+c][piece][id] == dist[tempo][piece][target]+1 && replace[r*N+c][piece][id] > replace[tempo][piece][target])
                     {
                        replace[r*N+c][piece][id] = replace[tempo][piece][target];
                        q.add(r*N+c);
                        q.add(piece);
                        q.add(id);
                     }
                  }
            }
         }
         int minDist = 420420420;
         int minRep = 420420420;
         int lmao = N*N;
         for(int p=0; p < 3; p++)
         {
            if(minDist > dist[loc[lmao]][p][lmao])
            {
               minDist = dist[loc[lmao]][p][lmao];
               minRep = replace[loc[lmao]][p][lmao];
            }
            else if(minDist == dist[loc[lmao]][p][lmao] && minRep > replace[loc[lmao]][p][lmao])
               minRep = replace[loc[lmao]][p][lmao];
         }
         System.out.println(minDist+" "+minRep);
      }
   }

import java.util.*;
import java.io.*;

public class Main {
	public static void main(String args[]) {new Main().run();}

	FastReader in = new FastReader();
	PrintWriter out = new PrintWriter(System.out);
	void run(){
		work();
		out.flush();
	}
	long mod=1000000007;
	long inf=Long.MAX_VALUE;
	long gcd(long a,long b) {
		return a==0?b:gcd(b%a,a);
	}
	int n;
	int[][] dir=new int[][] {{1,2},{2,1},{-1,2},{-2,1},{1,-2},{2,-1},{-1,-2},{-2,-1}};
	void work() {
		n=ni();
		int[][] rec=new int[n*n][];
		for(int i=0;i<n;i++) {
			for(int j=0;j<n;j++) {
				rec[ni()-1]=new int[] {i,j};
			}
		}
		int[][] dp=new int[3][2];//车相马,step replacement
		for(int i=0;i<n*n-1;i++) {
			int[][] ndp=new int[3][2];
			for(int[] d:ndp) {
				Arrays.fill(d, 99999999);
			}
			for(int j=0;j<3;j++) {
				int[][] A=bfs(rec[i],rec[i+1],j);
				for(int k=0;k<3;k++) {
					int v1=dp[j][0]+A[k][0];
					int v2=dp[j][1]+A[k][1];
					if(v1<ndp[k][0]||(v1==ndp[k][1]&&v2<ndp[k][1])) {
						ndp[k][0]=v1;
						ndp[k][1]=v2;
					}
				}
			}
			dp=ndp;
		}
		int s=99999999,r=99999999;
		for(int[] d:dp) {
			if(d[0]<s) {
				s=d[0];
				r=d[1];
			}else if(d[0]==s&&d[1]<r) {
				r=d[1];
			}
		}
		out.println(s+" "+r);
	}

	private int[][] bfs(int[] start, int[] end, int state) {
		int[][][][] rec=new int[n][n][3][2];
		for(int i=0;i<n;i++) {
			for(int j=0;j<n;j++) {
				for(int k=0;k<3;k++) {
					rec[i][j][k][0]=99999999;
					rec[i][j][k][1]=99999999;
				}
			}
		}
		rec[start[0]][start[1]][state][0]=0;
		rec[start[0]][start[1]][state][1]=0;
		LinkedList<int[]> queue=new LinkedList<>();
		queue.add(new int[] {start[0],start[1],0,0,state});
		while(queue.size()>0) {
			int[] q=queue.poll();
			int x=q[0],y=q[1],step=q[2],rep=q[3],s=q[4];
			for(int i=0;i<3;i++) {
				int nstep=step+1;
				int nrep=rep+1;
				if(rec[x][y][i][0]>nstep||(rec[x][y][i][0]==nstep&&rec[x][y][i][1]>nrep)) {
					rec[x][y][i][0]=nstep;
					rec[x][y][i][1]=nrep;
					queue.add(new int[] {x,y,nstep,nrep,i});
				}
			}
			if(s==0) {
				for(int i=0;i<n;i++) {
					int nstep=step+1;
					if(rec[i][y][s][0]>nstep||(rec[i][y][s][0]==nstep&&rec[i][y][s][1]>rep)) {
						rec[i][y][s][0]=nstep;
						rec[i][y][s][1]=rep;
						queue.add(new int[] {i,y,nstep,rep,s});
					}
					if(rec[x][i][s][0]>nstep||(rec[x][i][s][0]==nstep&&rec[x][i][s][1]>rep)) {
						rec[x][i][s][0]=nstep;
						rec[x][i][s][1]=rep;
						queue.add(new int[] {x,i,nstep,rep,s});
					}
				}
			}else if(s==1) {
				for(int i=-n;i<n;i++) {
					int nstep=step+1;
					int nx=x+i,ny=y+i;
					if(nx>=0&&nx<n&&ny>=0&&ny<n) {
						if(rec[nx][ny][s][0]>nstep||(rec[nx][ny][s][0]==nstep&&rec[nx][ny][s][1]>rep)) {
							queue.add(new int[] {nx,ny,nstep,rep,s});
							rec[nx][ny][s][0]=nstep;
							rec[nx][ny][s][1]=rep;
						}
					}
					nx=x-i;ny=y+i;
					if(nx<0||nx>=n||ny<0||ny>=n)continue;
					if(rec[nx][ny][s][0]>nstep||(rec[nx][ny][s][0]==nstep&&rec[nx][ny][s][1]>rep)) {
						queue.add(new int[] {nx,ny,nstep,rep,s});
						rec[nx][ny][s][0]=nstep;
						rec[nx][ny][s][1]=rep;
					}
				}
			}else {
				for(int i=0;i<8;i++) {
					int nx=x+dir[i][0],ny=y+dir[i][1];
					int nstep=step+1;
					if(nx<0||nx>=n||ny<0||ny>=n)continue;
					if(rec[nx][ny][s][0]>nstep||(rec[nx][ny][s][0]==nstep&&rec[nx][ny][s][1]>rep)) {
						queue.add(new int[] {nx,ny,nstep,rep,s});
						rec[nx][ny][s][0]=nstep;
						rec[nx][ny][s][1]=rep;
					}
				}
			}
		}
		return rec[end[0]][end[1]];
	}

	//input
	@SuppressWarnings("unused")
	private ArrayList<Integer>[] ng(int n, int m) {
		ArrayList<Integer>[] graph=(ArrayList<Integer>[])new ArrayList[n];
		for(int i=0;i<n;i++) {
			graph[i]=new ArrayList<>();
		}
		for(int i=1;i<=m;i++) {
			int s=in.nextInt()-1,e=in.nextInt()-1;
			graph[s].add(e);
			graph[e].add(s);
		}
		return graph;
	}

	private ArrayList<long[]>[] ngw(int n, int m) {
		ArrayList<long[]>[] graph=(ArrayList<long[]>[])new ArrayList[n];
		for(int i=0;i<n;i++) {
			graph[i]=new ArrayList<>();
		}
		for(int i=1;i<=m;i++) {
			long s=in.nextLong()-1,e=in.nextLong()-1,w=in.nextLong();
			graph[(int)s].add(new long[] {e,w,i});
			graph[(int)e].add(new long[] {s,w});
		}
		return graph;
	}

	private int ni() {
		return in.nextInt();
	}

	private long nl() {
		return in.nextLong();
	}

	private String ns() {
		return in.next();
	}

	private long[] na(int n) {
		long[] A=new long[n];
		for(int i=0;i<n;i++) {
			A[i]=in.nextLong();
		}
		return A;
	}
	private int[] nia(int n) {
		int[] A=new int[n];
		for(int i=0;i<n;i++) {
			A[i]=in.nextInt();
		}
		return A;
	}
}

class FastReader
{
	BufferedReader br;
	StringTokenizer st;

	public FastReader()
	{
		br=new BufferedReader(new InputStreamReader(System.in));
	}

	public String next()
	{
		while(st==null || !st.hasMoreElements())//回车，空行情况
		{
			try {
				st = new StringTokenizer(br.readLine());
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return st.nextToken();
	}

	public int nextInt()
	{
		return Integer.parseInt(next());
	}

	public long nextLong()
	{
		return Long.parseLong(next());
	}
}

import java.util.*;
import java.io.*;

public class Main {
	public static void main(String args[]) {new Main().run();}

	FastReader in = new FastReader();
	PrintWriter out = new PrintWriter(System.out);
	void run(){
		work();
		out.flush();
	}
	long mod=1000000007;
	long inf=Long.MAX_VALUE;
	long gcd(long a,long b) {
		return a==0?b:gcd(b%a,a);
	}
	int n;
	int[][] dir=new int[][] {{1,2},{2,1},{-1,2},{-2,1},{1,-2},{2,-1},{-1,-2},{-2,-1}};
	void work() {
		n=ni();
		int[][] rec=new int[n*n][];
		for(int i=0;i<n;i++) {
			for(int j=0;j<n;j++) {
				rec[ni()-1]=new int[] {i,j};
			}
		}
		int[][] dp=new int[3][2];//车相马,step replacement
		for(int i=0;i<n*n-1;i++) {
			int[][] ndp=new int[3][2];
			for(int[] d:ndp) {
				Arrays.fill(d, 99999999);
			}
			for(int j=0;j<3;j++) {
				int[][] A=bfs(rec[i],rec[i+1],j);
				for(int k=0;k<3;k++) {
					int v1=dp[j][0]+A[k][0];
					int v2=dp[j][1]+A[k][1];
					if(v1<ndp[k][0]||(v1==ndp[k][1]&&v2<ndp[k][1])) {
						ndp[k][0]=v1;
						ndp[k][1]=v2;
					}
				}
			}
			dp=ndp;
		}
		int s=99999999,r=99999999;
		for(int[] d:dp) {
			if(d[0]<s) {
				s=d[0];
				r=d[1];
			}else if(d[0]==s&&d[1]<r) {
				r=d[1];
			}
		}
		out.println(s+" "+r);
	}

	private int[][] bfs(int[] start, int[] end, int state) {
		int[][][][] rec=new int[n][n][3][2];
		for(int i=0;i<n;i++) {
			for(int j=0;j<n;j++) {
				for(int k=0;k<3;k++) {
					rec[i][j][k][0]=99999999;
					rec[i][j][k][1]=99999999;
				}
			}
		}
		rec[start[0]][start[1]][state][0]=0;
		rec[start[0]][start[1]][state][1]=0;
		LinkedList<int[]> queue=new LinkedList<>();
		queue.add(new int[] {start[0],start[1],0,0,state});
		while(queue.size()>0) {
			int[] q=queue.poll();
			int x=q[0],y=q[1],step=q[2],rep=q[3],s=q[4];
			for(int i=0;i<3;i++) {
				int nstep=step+1;
				int nrep=rep+1;
				if(rec[x][y][i][0]>nstep||(rec[x][y][i][0]==nstep&&rec[x][y][i][1]>nrep)) {
					rec[x][y][i][0]=nstep;
					rec[x][y][i][1]=nrep;
					queue.add(new int[] {x,y,nstep,nrep,i});
				}
			}
			if(s==0) {
				for(int i=0;i<n;i++) {
					int nstep=step+1;
					if(rec[i][y][s][0]>nstep||(rec[i][y][s][0]==nstep&&rec[i][y][s][1]>rep)) {
						rec[i][y][s][0]=nstep;
						rec[i][y][s][1]=rep;
						queue.add(new int[] {i,y,nstep,rep,s});
					}
					if(rec[x][i][s][0]>nstep||(rec[x][i][s][0]==nstep&&rec[x][i][s][1]>rep)) {
						rec[x][i][s][0]=nstep;
						rec[x][i][s][1]=rep;
						queue.add(new int[] {x,i,nstep,rep,s});
					}
				}
			}else if(s==1) {
				for(int i=-n;i<n;i++) {
					int nstep=step+1;
					int nx=x+i,ny=y+i;
					if(nx<0||nx>=n||ny<0||ny>=n)continue;
					if(rec[nx][ny][s][0]>nstep||(rec[nx][ny][s][0]==nstep&&rec[nx][ny][s][1]>rep)) {
						queue.add(new int[] {nx,ny,nstep,rep,s});
						rec[nx][ny][s][0]=nstep;
						rec[nx][ny][s][1]=rep;
					}
					nx=x-i;ny=y+i;
					if(nx<0||nx>=n||ny<0||ny>=n)continue;
					if(rec[nx][ny][s][0]>nstep||(rec[nx][ny][s][0]==nstep&&rec[nx][ny][s][1]>rep)) {
						queue.add(new int[] {nx,ny,nstep,rep,s});
						rec[nx][ny][s][0]=nstep;
						rec[nx][ny][s][1]=rep;
					}
				}
			}else {
				for(int i=0;i<8;i++) {
					int nx=x+dir[i][0],ny=y+dir[i][1];
					int nstep=step+1;
					if(nx<0||nx>=n||ny<0||ny>=n)continue;
					if(rec[nx][ny][s][0]>nstep||(rec[nx][ny][s][0]==nstep&&rec[nx][ny][s][1]>rep)) {
						queue.add(new int[] {nx,ny,nstep,rep,s});
						rec[nx][ny][s][0]=nstep;
						rec[nx][ny][s][1]=rep;
					}
				}
			}
		}
		return rec[end[0]][end[1]];
	}

	//input
	@SuppressWarnings("unused")
	private ArrayList<Integer>[] ng(int n, int m) {
		ArrayList<Integer>[] graph=(ArrayList<Integer>[])new ArrayList[n];
		for(int i=0;i<n;i++) {
			graph[i]=new ArrayList<>();
		}
		for(int i=1;i<=m;i++) {
			int s=in.nextInt()-1,e=in.nextInt()-1;
			graph[s].add(e);
			graph[e].add(s);
		}
		return graph;
	}

	private ArrayList<long[]>[] ngw(int n, int m) {
		ArrayList<long[]>[] graph=(ArrayList<long[]>[])new ArrayList[n];
		for(int i=0;i<n;i++) {
			graph[i]=new ArrayList<>();
		}
		for(int i=1;i<=m;i++) {
			long s=in.nextLong()-1,e=in.nextLong()-1,w=in.nextLong();
			graph[(int)s].add(new long[] {e,w,i});
			graph[(int)e].add(new long[] {s,w});
		}
		return graph;
	}

	private int ni() {
		return in.nextInt();
	}

	private long nl() {
		return in.nextLong();
	}

	private String ns() {
		return in.next();
	}

	private long[] na(int n) {
		long[] A=new long[n];
		for(int i=0;i<n;i++) {
			A[i]=in.nextLong();
		}
		return A;
	}
	private int[] nia(int n) {
		int[] A=new int[n];
		for(int i=0;i<n;i++) {
			A[i]=in.nextInt();
		}
		return A;
	}
}

class FastReader
{
	BufferedReader br;
	StringTokenizer st;

	public FastReader()
	{
		br=new BufferedReader(new InputStreamReader(System.in));
	}

	public String next()
	{
		while(st==null || !st.hasMoreElements())//回车，空行情况
		{
			try {
				st = new StringTokenizer(br.readLine());
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return st.nextToken();
	}

	public int nextInt()
	{
		return Integer.parseInt(next());
	}

	public long nextLong()
	{
		return Long.parseLong(next());
	}
}