#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>
using namespace __gnu_pbds;
using namespace std;

#define int long long
#define mp make_pair
#define pb push_back

#pragma GCC optimize("O3")
#pragma GCC optimize("unroll-loops")

constexpr int INF = (int)1e18;
constexpr int N = (int)2e5 + 111;
constexpr int md = (int)998244353;

mt19937 rnd(chrono::steady_clock::now().time_since_epoch().count());

int g[555][555];
set<int> pos[555][555];
set<int> st[555];
int cnt[555][555];

int n,m;
vector<pair<int,int>> Queries;
void Swap(int a,int b){
    int x1 = a/m, y1 = a%m;
    int x2 = b/m, y2 = b%m;
    pos[x1][g[x1][y1]].erase(a);
    pos[x2][g[x2][y2]].erase(b);
    swap(g[x1][y1],g[x2][y2]);
    pos[x1][g[x1][y1]].insert(a);
    pos[x2][g[x2][y2]].insert(b);
    Queries.pb(mp(a+1,b+1));
    return;
}

struct edge{
    bool active = true;
    int to;
    int x;
    bool used = false;
    edge(int to,int x):to(to),x(x){}
};

vector<edge> gr[N];
vector<edge> gtr[N];

int EmptyPos;

//void Swap(int x1,int a,int x2,int b){
//    assert(st[x1].count(a) && st[x2].count(b));
//    int p1 = *pos[x1][a].begin();
//    int p2 = *pos[x2][b].begin();
//    Swap(p1,p2,Queries);
//    return;
//}

int start = -1;
vector<pair<int,int>> best,cur;
int cntNotOK = 0;
bool used[N];

void dfs(int v){
    used[v] = 1;
    if(start == v){
        if(best.size() > cur.size() && cntNotOK == 0)
            best = cur;
        if(cntNotOK == 0)
            return;
    }
    for(auto& e : gtr[v]){
        int& to = e.to, x = e.x;
        if(!st[v].count(x) && cnt[to][x] > 1){
            st[v].insert(x);
            cnt[to][x]--;
            cur.pb(mp(to,x));
            cntNotOK -= (st[v].size() == m);
            dfs(to);
            cntNotOK += (st[v].size() == m);
            st[v].erase(x);
            cur.pop_back();
            cnt[to][x]++;
        }
    }
    return;
}

void solve(){
    cin >> n >> m;

    for(int i = 0; i < n; i++){
        for(int j = 0; j < m; j++){
            cin >> g[i][j];
            pos[i][g[i][j]].insert(i*m+j);
            st[i].insert(g[i][j]);
            cnt[i][g[i][j]]++;
        }
        cntNotOK += (st[i].size() < m);
    }

    for(int j = 1; j <= m; j++){
        for(int i = 0; i < n; i++){
            if(pos[i][j].size() <= 1)
                continue;
            for(int t = 0; t < n; t++){
                if(i == t || pos[t][j].size() > 0)
                    continue;
                gr[i].pb(edge(t,j));
                gtr[t].pb(edge(i,j));
//                cerr << "edge from " << i+1 << " to " << t+1 << " using " << j << "\n";
            }
        }
    }

//    for(auto& e : gtr[4]){
//        cerr << "to " << e.to+1 << " " << e.x << "\n";
//    }

    int AdditionalPos = n*m;

    for(int i = 0; i < n; i++){
        if(used[i])
            continue;
        int p = -1, to = -1;
        for(auto& e : gtr[i]){
            if(!e.active)
                continue;
            int& to = e.to;
            int& x = e.x;
            if(st[i].count(x) && cnt[to][x] > 1){
                e.active = false;
                continue;
            }
            p = x;
            break;
        }
        if(p == -1)
            continue;
        best.clear();
        best.resize(n*m+22);
        cur.clear();
        start = i;
        dfs(i);
//        cerr << "best: ";
//        for(auto&[x,y] : best){
//            cerr << x+1 << " " << y << "\n";
//        }
//        cerr << "\n";
        int sz = best.size();
        for(int j = 0; j < sz; j++){
            best.pb(best[j]);
            used[best[j].first] = 1;
        }
        int start = 0;
        bool great = false;
        int take = -1;
        int S = g[i][0];
        int P = i*m;
        for(int j = 0; j < sz; j++){
            int y = best[j+sz].second;
            int last = best[j+sz].first;
            int f = best[j].first;
            if(cnt[f][y] > 1){
                start = j;
                S = y;
                P = *pos[f][y].begin();
                great = true;
                break;
            }
        }
        Swap(P,AdditionalPos);
        pos[i][S].erase(P);
        int curEmpty = P;
        int pr = i;
        int prX,prY;
        for(int i = start+1; i <= start + sz - 1; i++){
            int& x = best[i].first, y = best[i].second;
            int p1 = *pos[x][y].begin();
            Swap(p1,curEmpty);
            st[pr].insert(y);
            cnt[x][y]--;
            cnt[pr][y]++;
            pr = x;
            curEmpty = p1;
        }
        if(great){
            cnt[curEmpty/m][g[n*m][0]]++;
            Swap(AdditionalPos,curEmpty);
        } else {
            for(int i = start + sz; i <= start + sz; i++){
                int& x = best[i].first, y = best[i].second;
                int p1 = *pos[x][y].begin();
                Swap(p1,curEmpty);
                st[pr].insert(y);
                cnt[x][y]--;
                cnt[pr][y]++;
                pr = x;
                curEmpty = p1;
            }
            cnt[P/m][g[n*m][0]]++;
            Swap(AdditionalPos,P);
        }
    }

    cout << Queries.size() << "\n";
    for(auto&[x,y] : Queries){
        cout << x << " " << y << '\n';
    }

    for(int i = 0; i < n; i++){
        for(int j = 0; j < m; j++){
            cerr << g[i][j] << " ";
        }
        cerr << "\n";
    }

    return;
}

signed main(){
    ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
    int tests = 1;
//    cin >> tests;
    for(int test = 1; test <= tests; test++){
        solve();
    }
    return 0;
}
/**
make VUDOI great again.

abcaabbaccccb
5
1 13
2 8
1 9
4 12
4 7

5 6
2 4 6 0 1 5
2 4 5 1 3 6
1 6 5 2 3 4
3 6 5 4 1 2
4 3 5 2 1 6

3

5 -> 3 -> 2 -> 3 -> 5 -> 4

1 2 4 4 5 6
1 2 3 4 5 5
1 1 2 3 6 6
1 2 3 5 5 6
2 3 3 4 4 6

8
29 31 4 5
15 29 2 3
12 15 1 2
18 12 3 4
27 18 4 5
22 27 3 4
4 22 0  1
31 4

5 6
2 0 6 4 1 5
2 4 6 1 3 5
4 5 1 2 3 6
3 6 4 5 1 2
1 3 5 2 3 6

4

**/
