/*
* Problem :: 16236 / 아기 상어
*
* Kind :: BFS
*
* Insight
* - BFS 한 번으로는 안될 것 같다...
* + 아기 상어의 위치에 따라 먹을 물고기가 계속 달라진다
* + 아기상어가 이동할 때마다 BFS 돌려도 시간 초과가 나지 않을려나?
* # O(전체칸의 개수 * BFS 한 번 돌리기) = O(20*20 * 20*20)
* = O(160000) < O(2*10^8)
* 충분할 것 같다
*
* Point
* - 아기 상어의 위치에 따른 물고기 선호도는 정렬로 해결하였다
* + BFS 하면서 만나는 첫 번째 먹을 수 있는 물고기가
* 가장 위(또는 왼쪽)에 있지 않을 수도 있다!
* + 그냥 먹을 수 있는 물고기를 다 찾은 후에
* 아기 상어의 입맛대로 정렬해서 먹어야 하는 물고기를 알아내자
*/
//
// BOJ
// ver.C++
//
// Created by GGlifer
//
// Open Source
#include <iostream>
#include <queue>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
#define endl '\n'
// Set up : Global Variables
int N;
int space[20][20];
struct Point { int y, x; };
int dy[4] = {+1, 0, 0, -1};
int dx[4] = {0, +1, -1, 0};
struct BabyShark { Point pos; int size; int feed; } babyShark;
struct Fish { int dist; Point pos; };
// Set up : Functions Declaration
bool operator < (const Fish &u, const Fish &v);
bool isValid(int y, int x);
Fish bfs(Point s);
int main()
{
// Set up : I/O
ios::sync_with_stdio(false);
cin.tie(nullptr);
// Set up : Input
cin >> N;
for (int i=0; i<N; i++) {
for (int j=0; j<N; j++) {
cin >> space[i][j];
if (space[i][j] == 9) {
babyShark.pos = {i, j}; /* 아기 상어 위치 초기화 */
space[i][j] = 0; /* 아기 상어의 위치는 계속 추적할 테니까,
* 그냥 초기 아기 상어의 위치는 빈칸으로 만들어둠 */
}
}
}
// Process
babyShark.size = 2; /* 아기 상어 크기 초기화 */
babyShark.feed = 0; /* 아기 상어 먹이 먹은 횟수 초기화 */
int time = 0;
while (true) {
Fish target = bfs(babyShark.pos); /* 먹어야 할 물고기를 찾음 */
if (target.dist == -1) break; /* 먹어야 할 물고기의 거리가 -1 이면
* 즉, 먹을 수 있는 물고기가 없으면 종료 */
time += target.dist; /* 물고기 거리만큼 흐른 시간 추가 */
babyShark.pos = target.pos; /* 아기 상어의 위치를 물고기 위치로 갱신 */
space[target.pos.y][target.pos.x] = 0; /* 아기상어가 물고기를 먹어서 빈칸이 됨 */
babyShark.feed++; /* 아기 상어 먹이 먹은 횟수 증가 */
/* 아기 상어 먹이 먹은 횟수와 크기가 같으면 */
if (babyShark.feed == babyShark.size) {
babyShark.size++; /* 아기 상어 사이즈업 */
babyShark.feed = 0; /* 아기 상어 먹이 먹은 횟수 초기화 */
}
}
// Control : Output
cout << time << endl;
}
// Helper Functions
bool operator < (const Fish &u, const Fish &v)
/* 아기 상어의 입맛대로 물고기를 정렬하기 위한 비교 함수 */
{
if (u.dist != v.dist) return u.dist < v.dist; /* 거리가 가까워야 됨 */
if (u.pos.y != v.pos.y) return u.pos.y < v.pos.y; /* 똑같이 가까우면, 위에 있어야 됨 */
return u.pos.x < v.pos.x; /* 똑같이 가깝고 위에 있으면, 왼쪽에 있어야 됨 */
}
bool isValid(int y, int x)
/* 주어진 좌표가 유효하면 true 를 반환, 그 외 false 를 반환 */
{
return y >= 0 && y < N && x >= 0 && x < N;
}
Fish bfs(Point s)
/* 먹어야 하는 물고기의 정보(거리와 위치)를 반환,
* 먹을 수 있는 물고기가 없다면 (int)-1 로 채워진 물고기를 반환 */
{
queue<Point> que;
bool isVisited[N][N];
memset(isVisited, false, sizeof(isVisited));
vector<Fish> fishes; /* 여기에 먹을 수 있는 물고기 정보가 저장됨 */
que.push(s);
isVisited[s.y][s.x] = true;
int dist = -1;
while (not(que.empty())) {
int size = que.size();
dist++;
while (size--) {
Point c = que.front(); que.pop();
/* 빈칸이 아니고, 상어의 크기보다 작은 물고기가 있으면 */
if (space[c.y][c.x] != 0 && space[c.y][c.x] < babyShark.size) {
/* 일단 물고기 정보를 저장 */
fishes.push_back({dist, c});
}
for (int i=0; i<4; i++) {
int ny = c.y + dy[i];
int nx = c.x + dx[i];
/* 좌표가 유효하고, 방문하지 않았고,
* 빈칸이거나 상어의 크기보다 같거나 작은 물고기가 있으면 */
if (isValid(ny, nx) && not(isVisited[ny][nx])
&& space[ny][nx] <= babyShark.size)
{
/* 이동가능 */
isVisited[ny][nx] = true; /* 방문 표시 */
que.push({ny, nx}); /* Queue 에 좌표를 넣어줌 */
}
}
}
}
/* 먹을 수 있는 물고기가 없으면 */
if (fishes.empty())
/* (int)-1 로 채워진 물고기 정보 반환 */
return {-1, {-1, -1}};
/* 먹을 수 있는 물고기가 있으면 */
else {
/* 아기 상어 입맛대로 정렬 */
sort(fishes.begin(), fishes.end());
/* 가장 앞의 물고기 정보(이 물고기가 아기 상어가 먹을 물고기)를 반환 */
return fishes.front();
}
}