#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
#define MIN(a,b) a>b ? b:a;
#define MAX(a,b) a<b ? b:a;
const int oo = 0x3f3f3f3f;
//문제 : NxN크기의 보드에서, 블록 상태가 주어졌을 때(상황)
// 최대 5번까지 이동시켜서 만들 수 있는 가장 큰 블록의 값을 구하시오.
//동치 : 최대 5번까지 이동시킬 때, 나올 수 있는 모든 경우의 수를 구해서
// 그 중에 제일 큰 블록의 값을 find하기.
//따라서 가능한 모든 경우를 구하되, 조건을 지켜가며 bfs하기. 흠? 이것도 결국 조건인데?
//조건 : 상 하 좌 우로 밀때, 밀리는 곳부터 우선 처리되어야 하며, 한번만 밀려야 함.(즉, 한번 밀린건지, 아닌건지 판별 필요함)
int N;
//int backup_board[21][21];
int ans = -oo;
int find_max( int board[21][21])
{
int ret = -oo;
for (int x = 1; x <= N; x++)
for (int y = 1; y <= N; y++)
{
ret = MAX(ret, board[x][y]);
}
return ret;
}
//조건 해석하기
void up( int board[21][21], int temp[21][21])
{
int canjoin[21][21] = { 0, };//yes가 0 no가 1
for (int y = 1; y <= N; y++)
{
int t_idxx = 0;//들어온 원소 수라고 생각 최초는 empthy
for (int x = 1; x <= N; x++)
{
if (board[x][y] == 0) continue;
//board[x][y]에 뭔가 있을때..
if (t_idxx == 0) {//처음껀 그냥 옮겨주고
t_idxx++;
temp[t_idxx][y] = board[x][y];
}
else {//나머지의 경우는 같은거 있는지 검사
if (temp[t_idxx][y] == board[x][y] && canjoin[t_idxx][y] == 0) {
temp[t_idxx][y] = temp[t_idxx][y] * 2;
//t_idxx는 그대로 유지
canjoin[t_idxx][y] = 1;
}
else {
t_idxx++;
temp[t_idxx][y] = board[x][y];
}
}
}
t_idxx++;
while( t_idxx <= N) temp[t_idxx++][y] = 0;
}
}
void down( int board[21][21], int temp[21][21])
{
int canjoin[21][21] = { 0, };//yes가 0 no가 1
for (int y = 1; y <= N;y++) {
int t_idxx = N + 1;//들어온 원소 수라고 생각 최초는 empthy
for (int x = N; x >= 1;x--) {
if (board[x][y] == 0) continue;
//board[x][y]에 뭔가 있을때..
if (t_idxx > N) {//처음껀 그냥 옮겨주고
t_idxx--;
temp[t_idxx][y] = board[x][y];
}
else {//나머지의 경우는 같은거 있는지 검사
if (temp[t_idxx][y] == board[x][y] && canjoin[t_idxx][y] == 0) {
temp[t_idxx][y] = temp[t_idxx][y] * 2;
//t_idxx는 그대로 유지
canjoin[t_idxx][y] = 1;
}
else {
t_idxx--;
temp[t_idxx][y] = board[x][y];
}
}
}
t_idxx--;
while( t_idxx >= 1) temp[t_idxx--][y] = 0;
}
}
void left( int board[21][21], int temp[21][21])
{
int canjoin[21][21] = { 0, };//yes가 0 no가 1
for (int x = 1; x <= N;x++) {
int t_idxx = 0;//들어온 원소 수라고 생각 최초는 empthy
for (int y = 1; y <= N;y++) {
if (board[x][y] == 0) continue;
//board[x][y]에 뭔가 있을때..
if (t_idxx == 0) {//처음껀 그냥 옮겨주고
t_idxx++;
temp[x][t_idxx] = board[x][y];
}
else {//나머지의 경우는 같은거 있는지 검사
if (temp[x][t_idxx] == board[x][y] && canjoin[x][t_idxx] == 0) {
temp[x][t_idxx] = temp[x][t_idxx] * 2;
//t_idxx는 그대로 유지
canjoin[x][t_idxx] = 1;
}
else {
t_idxx++;
temp[x][t_idxx] = board[x][y];
}
}
}
t_idxx++;
while( t_idxx <= N) temp[x][t_idxx++] = 0;
}
}
void right( int board[21][21], int temp[21][21])
{
int canjoin[21][21] = { 0, };//yes가 0 no가 1
for (int x = 1; x <= N;x++) {
int t_idxx = N + 1;//들어온 원소 수라고 생각 최초는 empthy
for (int y = N; y >= 1; y--) {
if (board[x][y] == 0) continue;
//board[x][y]에 뭔가 있을때..
if (t_idxx > N) {//처음껀 그냥 옮겨주고
t_idxx--;
temp[x][t_idxx] = board[x][y];
}
else {//나머지의 경우는 같은거 있는지 검사
if (temp[x][t_idxx] == board[x][y] && canjoin[x][t_idxx] == 0) {
temp[x][t_idxx] = temp[x][t_idxx] * 2;
//t_idxx는 그대로 유지
canjoin[x][t_idxx] = 1;
}
else {
t_idxx--;
temp[x][t_idxx] = board[x][y];
}
}
}
t_idxx--;
while( t_idxx >= 1) temp[x][t_idxx--] = 0;
}
}
//동치 분석으로 인한 bfs코드
void dfs( int count, int board[21][21])
{
int outboard[21][21] = {0};
if (count == 5)
{//재귀의 탈출조건(중요)
int candi = find_max( board);
ans = MAX(ans, candi);
return;
}
for (int i = 1;i <= 4; i++) //1=상, 2=하, 3=좌, 4=우
{
switch(i)
{
case 1:
up( board, outboard);
break;
case 2:
down( board, outboard);
break;
case 3:
left( board, outboard);
break;
case 4:
right( board, outboard);
break;
default:
break;
}
dfs(count + 1, outboard);
}
}
void print_board( int board[21][21])
{
for (int x = 1; x <= N; x++)
{
for (int y = 1; y <= N; y++)
{
cout << board[x][y] << ' ';
}
cout << '\n';
}
}
int main( void)
{
int board[21][21];
cin >> N;
for (int x = 1; x <= N;x++)
for (int y = 1; y <= N;y++)
{
cin >> board[x][y];
}
dfs( 0, board);
cout << ans;
return 0;
}