// General imports

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;
import java.util.StringTokenizer;

//Nearly correct solution...one/two bugs passed most testcases
public class Island {
  static int N, M;
  static int[][] grid;
  static boolean[][] visited;
  static Map<Integer, Integer> islands = new HashMap<>();

  public static void main(String[] args) throws IOException {
    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    StringTokenizer st = new StringTokenizer(br.readLine());
    N = Integer.parseInt(st.nextToken());
    M = Integer.parseInt(st.nextToken());
    grid = new int[N][M];
    visited = new boolean[N][M];
    for (int i = 0; i < N; i++) {
      st = new StringTokenizer(br.readLine());
      for (int j = 0; j < M; j++) {
        int cell = Integer.parseInt(st.nextToken());
        grid[i][j] = cell;
      }
    }
    br.close();
    for (int i = 0; i < N; i++) {
      for (int j = 0; j < M; j++) {
        if (visited[i][j])
          continue;
        if (grid[i][j] > 0) {
          islands.put(grid[i][j], islands.getOrDefault(grid[i][j], 0) + 1);
          search(grid[i][j], i, j);
        } else {
          visited[i][j] = true;
        }
      }
    }
    if (islands.size() == 0) {
      System.out.println(0);
      return;
    }
    for (Map.Entry<Integer, Integer> e : islands.entrySet()) {
      int terrainNumber = e.getKey();
      int islandNum = e.getValue();
      System.out.printf("%d %d%n", terrainNumber, islandNum);
    }
  }

  static void search(int val, int i, int j) {
    if (visited[i][j])
      return;
    visited[i][j] = true;
    for (int dir = 0; dir < 4; dir++) {
      switch (dir) {
        case 0:
          if (j + 1 < M && grid[i][j + 1] == val) {
            search(val, i, j + 1);
          }
          break;
        case 1:
          if (i + 1 < N && grid[i + 1][j] == val) {
            search(val, i + 1, j);
          }
          break;
        case 2:
          if (j - 1 >= 0 && grid[i][j - 1] == val) {
            search(val, i, j - 1);
          }
          break;
        case 3:
          if (i - 1 >= 0 && grid[i - 1][j] == val) {
            search(val, i - 1, j);
          }
          break;
      }
    }
  }
}
