#include <bits/stdc++.h>
using namespace std;
template <typename T, typename U>
ostream &operator<<(ostream &os, const pair<T, U> &_p) {
  return os << "(" << _p.first << "," << _p.second << ")";
}
template <typename T>
ostream &operator<<(ostream &os, const vector<T> &_V) {
  bool f = true;
  os << "[";
  for (auto v : _V) {
    os << (f ? "" : ",") << v;
    f = false;
  }
  return os << "]";
}
template <typename T>
ostream &operator<<(ostream &os, const set<T> &_S) {
  bool f = true;
  os << "(";
  for (auto s : _S) {
    os << (f ? "" : ",") << s;
    f = false;
  }
  return os << ")";
}
template <typename T, typename U>
ostream &operator<<(ostream &os, const map<T, U> &_M) {
  return os << set<pair<T, U>>(_M.begin(), _M.end());
}
const signed long long INF = 1000000100;
const long double EPS = 1e-9;
struct FindUnion {
  int *S, *R;
  FindUnion(int n) : S(new int[n + 1]), R(new int[n + 1]) {
    for (int(i) = (0); (i) <= (n); (i)++) {
      S[i] = i;
      R[i] = 1;
    }
  }
  int find(int a) {
    if (S[a] == a)
      return a;
    else
      return S[a] = find(S[a]);
  }
  bool join(int a, int b) {
    a = find(a);
    b = find(b);
    if (a == b) return false;
    if (R[a] < R[b]) {
      S[a] = b;
      R[b] += R[a];
    } else {
      S[b] = a;
      R[a] += R[b];
    }
    return true;
  }
  ~FindUnion() {
    delete[] S;
    delete[] R;
  }
};
vector<tuple<int, int, int>> mst(vector<tuple<int, int, int>> &edges, int n) {
  FindUnion f(n + 1);
  sort(edges.begin(), edges.end());
  vector<tuple<int, int, int>> ret;
  for (auto &e : edges) {
    int w, a, b;
    tie(w, a, b) = e;
    if (f.join(a, b)) {
      ret.emplace_back(w, a, b);
    }
  }
  return ret;
}
const int MAXN = 2525;
int A[MAXN][MAXN];
int N;
void read_data() {
  scanf("%d", &N);
  for (int(i) = (1); (i) <= (N); (i)++)
    for (int(j) = (1); (j) <= (N); (j)++) scanf("%d", &A[i][j]);
}
vector<pair<int, int>> G[MAXN];
bool V[MAXN];
bool result = true;
void dfs(int v, int p, int start, int max_edge) {
  if (v != start and max_edge < A[start][v]) {
    result = false;
  }
  for (auto &e : G[v])
    if (e.first != p) {
      dfs(e.first, v, start, max(max_edge, e.second));
    }
}
bool solve() {
  for (int(i) = (1); (i) <= (N); (i)++)
    for (int(j) = (1); (j) <= (N); (j)++)
      if (A[i][j] != A[j][i]) {
        return false;
      }
  for (int(i) = (1); (i) <= (N); (i)++)
    if (A[i][i] != 0) {
      return false;
    }
  vector<tuple<int, int, int>> edges;
  for (int(i) = (1); (i) <= (N); (i)++)
    for (int(j) = (i + 1); (j) <= (N); (j)++) {
      edges.emplace_back(A[i][j], i, j);
    }
  vector<tuple<int, int, int>> mst_edges = mst(edges, N);
  for (auto &e : mst_edges) {
    int w, a, b;
    tie(w, a, b) = e;
    G[a].emplace_back(b, w);
    G[b].emplace_back(a, w);
  }
  for (int(i) = (1); (i) <= (N); (i)++) {
    dfs(i, -1, i, -INF);
    if (!result) {
      return false;
    }
  }
  return true;
}
int main() {
  read_data();
  printf(solve() ? "MAGIC\n" : "NOT MAGIC\n");
}
