#include <bits/stdc++.h>
using namespace std;
const int inf = 1e9;
const long long Inf = 1e18;
const int mod = 1;
template <typename T = int>
T gi() {
  T x = 0, o = 1;
  char ch = getchar();
  while ((ch < '0' || ch > '9') && ch != '-') ch = getchar();
  if (ch == '-') o = -1, ch = getchar();
  while (ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar();
  return x * o;
}
template <typename T>
bool chkmax(T &a, T b) {
  return a < b ? a = b, 1 : 0;
};
template <typename T>
bool chkmin(T &a, T b) {
  return a > b ? a = b, 1 : 0;
};
int add(int a, int b) { return a + b >= mod ? a + b - mod : a + b; }
int sub(int a, int b) { return a - b < 0 ? a - b + mod : a - b; }
void inc(int &a, int b) { a = (a + b >= mod ? a + b - mod : a + b); }
void dec(int &a, int b) { a = (a - b < 0 ? a - b + mod : a - b); }
string to_string(string s) { return '"' + s + '"'; }
string to_string(const char *s) { return to_string((string)s); }
template <typename A, typename B>
string to_string(pair<A, B> p) {
  return "(" + to_string(p.first) + "," + to_string(p.second) + ")";
}
template <typename T>
string to_string(T v) {
  int fst = 1;
  string ret = "{";
  for (auto x : v) {
    if (!fst) ret += ",";
    fst = 0, ret += to_string(x);
  }
  ret += "}";
  return ret;
}
void dbg_out() { cerr << endl; }
template <typename Head, typename... Tail>
void dbg_out(Head H, Tail... T) {
  cerr << " " << to_string(H);
  dbg_out(T...);
}
template <typename T>
int qpow(int a, T b) {
  int ret = 1;
  while (b) {
    if (b & 1) ret = 1ll * ret * a % mod;
    a = 1ll * a * a % mod, b >>= 1;
  }
  return ret;
}
struct sgt_data {
  int mn = 0, cnt = 0;
};
struct sgt_tag {
  int val = 0;
};
struct segtree {
  int n = 0;
  vector<sgt_data> tr;
  vector<sgt_tag> tg;
  void reset(int _n) {
    n = _n;
    int N = (n << 2) + 10;
    tr.clear();
    tg.clear();
    tr.resize(N);
    tg.resize(N);
  }
  void apply(int x, int l, int r, int v) {
    tr[x].mn += v;
    tg[x].val += v;
  }
  void pushdown(int x, int l, int r) {
    int mid = (l + r) >> 1;
    if (tg[x].val)
      apply((x << 1), l, mid, tg[x].val),
          apply((x << 1 | 1), mid + 1, r, tg[x].val), tg[x].val = 0;
  }
  sgt_data merge(sgt_data a, sgt_data b) {
    sgt_data ret;
    ret.mn = min(a.mn, b.mn);
    ret.cnt = 0;
    if (ret.mn == a.mn) ret.cnt += a.cnt;
    if (ret.mn == b.mn) ret.cnt += b.cnt;
    return ret;
  }
  void pushup(int x) { tr[x] = merge(tr[(x << 1)], tr[(x << 1 | 1)]); }
  void build(int x, int l, int r) {
    tr[x].cnt = r - l + 1;
    if (l == r) return;
    int mid = (l + r) >> 1;
    build((x << 1), l, mid), build((x << 1 | 1), mid + 1, r);
    pushup(x);
  }
  template <typename... T>
  void _modify(int x, int l, int r, int L, int R, T... v) {
    if (L <= l && r <= R) return apply(x, l, r, v...);
    pushdown(x, l, r);
    int mid = (l + r) >> 1;
    if (L <= mid) _modify((x << 1), l, mid, L, R, v...);
    if (R > mid) _modify((x << 1 | 1), mid + 1, r, L, R, v...);
    pushup(x);
  }
  sgt_data _query(int x, int l, int r, int L, int R) {
    if (L <= l && r <= R) return tr[x];
    pushdown(x, l, r);
    int mid = (l + r) >> 1;
    if (R <= mid)
      return _query((x << 1), l, mid, L, R);
    else if (L > mid)
      return _query((x << 1 | 1), mid + 1, r, L, R);
    else
      return merge(_query((x << 1), l, mid, L, R),
                   _query((x << 1 | 1), mid + 1, r, L, R));
  }
  segtree(int n = 0) { reset(n); }
  void build() { build(1, 1, n); }
  template <typename... T>
  void modify(int l, int r, T... v) {
    _modify(1, 1, n, l, r, v...);
  }
  sgt_data query(int l, int r) { return _query(1, 1, n, l, r); }
} sgt;
struct lct_data {
  int sum = 0;
  lct_data() {}
  lct_data(int _sum) { sum = _sum; }
};
struct lct_tag {
  int rev = 0;
  lct_tag() {}
  lct_tag(int _rev) { rev = _rev; }
};
struct LCT {
  vector<vector<int>> ch;
  vector<int> fa;
  vector<lct_data> val, tr;
  vector<lct_tag> tg;
  void reset(int n) {
    const int N = n + 10;
    ch.clear(), fa.clear();
    val.clear();
    tr.clear();
    tg.clear();
    ch.resize(N), fa.resize(N);
    val.resize(N);
    tr.resize(N);
    tg.resize(N);
    for (auto &x : ch) x.resize(2);
  }
  LCT(int n = 0) { reset(n); }
  lct_data merge(lct_data a, lct_data b) {
    lct_data ret;
    ret.sum = a.sum ^ b.sum;
    return ret;
  }
  void apply(int x, bool y = 1, int z = 0) {
    if (y) tg[x].rev ^= 1, swap(ch[x][0], ch[x][1]);
    if (z) val[x].sum ^= z, tr[x].sum ^= z;
  }
  void pushdown(int x) {
    if (tg[x].rev) apply(ch[x][0]), apply(ch[x][1]), tg[x].rev = 0;
  }
  void pushup(int x) {
    tr[x] = merge(merge(tr[ch[x][0]], val[x]), tr[ch[x][1]]);
  }
  bool isroot(int x) { return ch[fa[x]][0] != x && ch[fa[x]][1] != x; }
  void rotate(int x) {
    int y = fa[x], z = fa[y], w = ch[y][1] == x;
    if (!isroot(y)) ch[z][ch[z][1] == y] = x;
    ch[y][w] = ch[x][w ^ 1];
    fa[ch[y][w]] = y;
    ch[x][w ^ 1] = y;
    fa[y] = x;
    fa[x] = z;
    pushup(y);
    pushup(x);
  }
  void pushall(int x) {
    if (!isroot(x)) pushall(fa[x]);
    pushdown(x);
  }
  void splay(int x) {
    pushall(x);
    while (!isroot(x)) {
      int y = fa[x], z = fa[y];
      if (!isroot(y)) (ch[y][0] == x) ^ (ch[z][0] == y) ? rotate(x) : rotate(y);
      rotate(x);
    }
  }
  void access(int x) {
    for (int t = 0; x; t = x, x = fa[x]) splay(x), ch[x][1] = t, pushup(x);
  }
  void makeroot(int x) {
    access(x);
    splay(x);
    apply(x);
  }
  void link(int x, int y) {
    makeroot(x);
    fa[x] = y;
  }
  void cut(int x, int y) {
    makeroot(x);
    access(y);
    splay(y);
    if (ch[y][0] == x && !ch[x][1]) ch[y][0] = fa[x] = 0, pushup(y);
  }
  void split(int x, int y) {
    makeroot(x);
    access(y);
    splay(y);
  }
  int findroot(int x) {
    access(x);
    splay(x);
    while (ch[x][0]) x = ch[x][0];
    splay(x);
    return x;
  }
  template <typename... T>
  void modify(int x, int y, T... v) {
    split(x, y);
    apply(y, v...);
  }
  lct_data query(int x, int y) {
    split(x, y);
    return tr[y];
  }
} lct;
const int N = 1e3 + 10;
int n, m, f[N][N], id[N][N], tot = 0, lp[N * N], dx[4] = {1, 0, 0, -1},
                             dy[4] = {0, 1, -1, 0};
pair<int, int> pos[N * N];
vector<int> get(int i) {
  vector<int> ret;
  int x = pos[i].first, y = pos[i].second;
  for (int d = 0; d < 4; d++) {
    int xx = x + dx[d], yy = y + dy[d];
    if (xx < 1 || xx > n || yy < 1 || yy > m) continue;
    ret.push_back(f[xx][yy]);
  }
  return ret;
}
bool check(int x, int now, int lim, vector<int> v) {
  if (now == int(v.size())) return 1;
  if (v[now] < lim || v[now] > x) return check(x, now + 1, lim, v);
  if (lct.findroot(v[now]) == lct.findroot(x)) return 0;
  lct.link(x, v[now]);
  bool ret = check(x, now + 1, lim, v);
  if (!ret) lct.cut(x, v[now]);
  return ret;
}
int main() {
  n = gi(), m = gi();
  for (int i = 1; i <= n; i++)
    for (int j = 1; j <= m; j++) f[i][j] = gi(), id[i][j] = ++tot;
  for (int i = 1; i <= n; i++)
    for (int j = 1; j <= m; j++) pos[f[i][j]] = make_pair(i, j);
  lct.reset(n * m), sgt.reset(n * m);
  for (int i = 1, j = 1; i <= n * m; i++) {
    vector<int> v = get(i);
    while (!check(i, 0, j, v)) {
      vector<int> d = get(j);
      for (auto x : d) lct.cut(j, x);
      ++j;
    }
    lp[i] = j;
  }
  sgt.build();
  long long ans = 0;
  for (int i = 1; i <= n * m; i++) {
    sgt.modify(lp[i], i, 1);
    vector<int> v = get(i);
    for (auto x : v)
      if (x >= lp[i] && x <= i) sgt.modify(lp[i], x, -1);
    ans += sgt.query(lp[i], i).cnt;
  }
  printf("%lld\n", ans);
  return 0;
}
