#include <bits/stdc++.h>
using namespace std;
#define F first
#define S second
const int N = 1e6+10;
const int logN = 20;
int str[N], floorlog[N];
int _rank[N], pos[N];
int cnt[N], _next[N];
bool bh[N], b2h[N];
bool smaller_first_char(int a, int b){
return str[a] < str[b];
}
void suffixSort(int n){
for (int i=0; i<n; ++i){
pos[i] = i;
}
sort(pos, pos + n, smaller_first_char);
for (int i=0; i<n; ++i){
bh[i] = i == 0 || str[pos[i]] != str[pos[i-1]];
b2h[i] = false;
}
for (int h = 1; h < n; h <<= 1){
int buckets = 0;
for (int i=0, j; i < n; i = j){
j = i + 1;
while (j < n && !bh[j]) j++;
_next[i] = j;
buckets++;
}
if (buckets == n) break; // We are done! Lucky bastards!
for (int i = 0; i < n; i = _next[i]){
cnt[i] = 0;
for (int j = i; j < _next[i]; ++j){
_rank[pos[j]] = i;
}
}
cnt[_rank[n - h]]++;
b2h[_rank[n - h]] = true;
for (int i = 0; i < n; i = _next[i]){
for (int j = i; j < _next[i]; ++j){
int s = pos[j] - h;
if (s >= 0){
int head = _rank[s];
_rank[s] = head + cnt[head]++;
b2h[_rank[s]] = true;
}
}
for (int j = i; j < _next[i]; ++j){
int s = pos[j] - h;
if (s >= 0 && b2h[_rank[s]]){
for (int k = _rank[s]+1; !bh[k] && b2h[k]; k++) b2h[k] = false;
}
}
}
for (int i=0; i<n; ++i){
pos[_rank[i]] = i;
bh[i] |= b2h[i];
}
}
for (int i=0; i<n; ++i){
_rank[pos[i]] = i;
}
}
int height[N];
// height[i] = length of the longest common prefix of suffix pos[i] and suffix pos[i+1]
void getHeight(int n){
for (int i=0; i<n; ++i) _rank[pos[i]] = i;
height[0] = 0;
for (int i=0, h=0; i<n; ++i){
if (_rank[i] > 0){
int j = pos[_rank[i]-1];
while (i + h < n && j + h < n && str[i+h] == str[j+h]) h++;
height[_rank[i]] = h;
if (h > 0) h--;
}
}
for(int i = 0;i<n-1;i++)
height[i] = height[i+1];
height[n-1] = 0;
}
int n, m;
int getInd(int i, int j){
return i * m + j;
}
int col = 0;
bool cmp(int i,int j){
return _rank[getInd(i, col)] < _rank[getInd(j, col)];
}
vector<int> vec;
long long ans = 0;
int arr[logN][N];
struct sparse_table{
int n;
sparse_table(int _n){
n = _n;
}
void storefloorlog(){
for(int i = 0; (1 << i) < N; i++){
for(int j = (1 << i); j < (1 << (i + 1)) && j < N; j++)
floorlog[j] = i;
}
}
int func(int x, int y){
return min(x, y);
}
void make(int A[], int n){
for(int i = n - 1; i >= 0; i--){
arr[0][i] = A[i];
for(int j = 1; i + (1 << j) - 1 <= n; j++){
arr[j][i] = func(arr[j - 1][i], arr[j - 1][i + (1 << (j - 1))]);
}
}
}
int get(int i, int j){
int k = floorlog[j - i + 1];
return func(arr[k][i], arr[k][j - (1 << k) + 1]);
}
inline int lcp(int i, int j){
if(i == j) return n * m - i;
i = _rank[i]; j = _rank[j];
if(i > j) swap(i, j);
return get(i, j - 1);
}
};
map<int, vector<int>> mp;
long long bad[N];
long long currans;
int par[N];
long long f(int i){
return (i * 1ll * (i + 1)) >> 1;
}
set<int> positions[N];
void init(){
currans = 0;
for(int j = 0; j < n; j++){
bad[j] = f(j) + f(n - j - 1);
currans += (j + 1) * 1ll * (n - j);
positions[j].clear();
positions[j].insert(j);
par[j] = j;
}
}
int root(int i){
return par[i] == i ? i : (par[i] =root(par[i]));
}
void _insert(int i, int v){
auto it = positions[i].upper_bound(v);
int hi = (it == positions[i].end() ? n : *it);
int lo = (it == positions[i].begin() ? -1 : *(--it));
bad[i] -= f(hi - lo - 1);
bad[i] += f(hi - v - 1) + f(v - lo - 1);
positions[i].insert(v);
}
void merge(int i, int j){
i = root(i); j = root(j);
if(i == j) return;
if(positions[i].size() < positions[j].size()) swap(i, j);
par[j] = i;
currans += bad[i];
currans -= (f(n) - bad[j]);
for(auto it : positions[j]) _insert(i, it);
positions[j].clear();
currans -= bad[i];
}
// O(n * m * log^2(n))
int s[N];
int main(){
scanf("%d %d", &n, &m);
int ind = 0;
for(int i = 0; i < n; i++){
vec.push_back(i);
for(int j = 0; j < m; j++){
scanf("%d", s + j);
str[ind++] = s[j];
}
}
suffixSort(n * m);
getHeight(n * m);
sparse_table st = sparse_table(n * m);
st.storefloorlog();
st.make(height, n * m);
for(col = 0; col < m; col++){
sort(vec.begin(), vec.end(), cmp);
mp.clear();
for(int i = 0; i < vec.size() - 1; i++){
int L = st.lcp(getInd(vec[i], col), getInd(vec[i + 1], col));
mp[-min(m - 1, (col + L - 1))].push_back(i);
}
init();
int curr = m - 1;
for(auto it : mp){
int newpos = -it.F;
ans += (curr - newpos) * 1ll * currans;
for(auto it2 : it.S) merge(vec[it2], vec[it2 + 1]);
curr = newpos;
}
ans += (curr - col + 1) * 1ll * currans;
}
printf("%lld\n", ans);
}