#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
const int MAXN = 305;
const int oo = 1e9+7;
int w[MAXN][MAXN], N;
int dx[MAXN], dy[MAXN];
int Ly[MAXN], slack[MAXN];
bool vx[MAXN], vy[MAXN];
bool Find(int i)
{///别忘记vx[i]赋值true,表示可以增广的时候达到了i点
vx[i] = true;
for(int j=1; j<=N; j++)
{
if( !vy[j] && dx[i]+dy[j] == w[i][j] )
{
vy[j] = true;
if( !Ly[j] || Find(Ly[j]) )
{
Ly[j] = i;
return true;
}
}
///else if(vy[j] == false)
/// slack[j] = min(slack[j], dx[i]+dy[j]-w[i][j]);
}
return false;
}
int KM()
{
int i, j, k;
for(i=1; i<=N; i++) while(1)
{///给每个点进行增广,找不到减去一个d,继续找
memset(vx, false, sizeof(vx));
memset(vy, false, sizeof(vy));
if( Find(i) == true) break;
int d = oo;
for(j=1; j<=N; j++) if( vx[j] )
for(k=1; k<=N; k++) if( !vy[k] )
{///用访问过的左边和未访问过的右边求出来一个最小的d
d = min( d, dx[j]+dy[k]-w[j][k]);
}
for(j=1; j<=N; j++)
{///左边的标杆减去d,右边的加上d,这样原来匹配的值没有改变
///减去d的目的是为了查找那个可以增广的边里面最大的那个
if(vx[j])dx[j] -= d;
if(vy[j])dy[j] += d;
}
}
int sum = 0;
for(i=1; i<=N; i++)
{
sum += w[ Ly[i] ][i];
}
return sum;
}
int main()
{
while(scanf("%d", &N) != EOF)
{
memset(dx, false, sizeof(dx));
memset(dy, false, sizeof(dy));
memset(Ly, false, sizeof(Ly));
for(int i=1; i<=N; i++)
for(int j=1; j<=N; j++)
{
scanf("%d", &w[i][j]);
dx[i] = max(dx[i], w[i][j]);
}
printf("%d\n", KM());
}
return 0;
}