description: You have received data from a Bubble bot. You know your task is to make factory facilities, but before you even start, you need to know how big the factory is and how many rooms it has. When you look at the data you see that you have the dimensions of the construction, which is in rectangle shape: N x M. 
  
  Then in the next N lines you have M numbers. These numbers represent factory tiles and they can go from 0 to 15. Each of these numbers should be looked in its binary form. Because from each number you know on which side the tile has walls. For example number 10 in it's binary form is 1010, which means that it has a wall from the North side, it doesn't have a wall from the East, it has a wall on the South side and it doesn't have a wall on the West side. So it goes North, East, South, West. 
  
  It is guaranteed that the construction always has walls on it's edges. The input will be correct. 
  
  Your task is to print the size of the rooms from biggest to smallest. 
  
  Input
  
  The first line has two numbers which are N and M, the size of the construction. Both are integers: 
  
  n (1 ≤ n ≤ 10^3)
  
  m (1 ≤ m ≤ 10^3)
  
  Next N x M numbers represent each tile of construction.
  
  Output
  
  Once you finish processing the data your output consists of one line sorted from biggest to smallest room sizes. 
  
  Example
  
  Input
  
  
  4 5
  9 14 11 12 13
  5 15 11 6 7
  5 9 14 9 14
  3 2 14 3 14
  
  
  Output
  
  
  9 4 4 2 1 
solution: // Problem: J. Robot Factory
  // Contest: Codeforces - Bubble Cup 14 - Finals Online Mirror (Unrated, ICPC Rules, Teams Preferred, Div. 2)
  // URL: https://codeforces.com/problemset/problem/1600/J
  // Memory Limit: 256 MB
  // Time Limit: 1000 ms
  // 
  // Powered by CP Editor (https://cpeditor.org)
  
  import java.io.*;
  import java.util.*;
  
  import static java.lang.Math.max;
  import static java.lang.Math.min;
  import static java.lang.Math.abs;
  
  
  /**
   * zxz 
   * problem link:
   **/
  public class Main {
      //put some data here
  	int [][] mp;
  	boolean [][] vis;
  	int n,m;
  	int [] stepx = {0,1,0,-1};
  	int [] stepy = {-1,0,1,0};
      /**
       * do some clean action before next testcase
       */
      private void clear() {
  
      }
  
  	int dfs(int x, int y){
  		int res = 1;
  		vis[x][y] = true;
  		for(int i = 0; i < 4; i++){
  			if(((1 << i) & mp[x][y]) == 0){
  				int tx = x + stepx[i];
  				int ty = y + stepy[i];
  				// System.out.println(x + " " + y + " : " + i + " " + mp[x][y]);
  				if(!vis[tx][ty]){
  					res += dfs(tx, ty);
  				}
  				
  			}
  		}
  		return res;
  	}
  
      private void solve() throws Exception {
          rf();
          n = ri();
          m = ri();
  		mp = new int[n][m];
  		for(int i = 0; i < n; i++){
  			mp[i] = readArr(m);
  		}
  		vis = new boolean[n][m];
  		ArrayList<Integer> size = new ArrayList<>();
  		for(int i = 0; i < n; i++){
  			for(int j = 0; j < m; j++){
  				if(!vis[i][j]){
  					size.add(dfs(i,j));
  				}
  			}
  		}
  		Collections.sort(size, (a, b)->{
  			return b-a;
  		});
  		for(int i : size){
  			sb.append(i + " ");
  		}
  		sb.append("\n");
      }
  
      private void run() throws Exception {
  
  //        rf();
  //        int T = ri();
          int T = 1;
  
          while (T-- > 0) {
              solve();
              if (T != 0) {
                  clear();
              }
          }
          printAns();
      }
  
      public static void main(String[] args) throws Exception {
          new Main().run();
      }
  
      StringBuilder sb = new StringBuilder();
      BufferedReader infile = new BufferedReader(new InputStreamReader(System.in));
      StringTokenizer st;
  
  	private void addAns(int a){
  		sb.append(a + "\n");
  	}
  	
  	private void addAns(long a){
  		sb.append(a + "\n");
  	}
  	
  	private void addAns(String s){
  		sb.append(s + "\n");
  	}
  
      private void rf() throws IOException {
          st = new StringTokenizer(infile.readLine());
      }
  
  
      private int ri() {
          return Integer.parseInt(st.nextToken());
      }
  
      private long rl() {
          return Long.parseLong(st.nextToken());
      }
  
  	private char [] rs2c(){
  		return st.nextToken().toCharArray();
  	}
  	
  	private String rs(){
  		return st.nextToken();
  	}
  
      private void printAns() {
          System.out.println(sb);
      }
  
      private int[] readArr(int N) throws Exception {
          int[] arr = new int[N];
          rf();
          for (int i = 0; i < N; i++) {
              arr[i] = Integer.parseInt(st.nextToken());
          }
          return arr;
      }
  
      private void print(int[] arr) {
          //for debugging only
          for (int x : arr) {
              System.out.print(x + " ");
          }
          System.out.println();
      }
      
      private void print(long[] arr) {
          //for debugging only
          for (long x : arr) {
              System.out.print(x + " ");
          }
          System.out.println();
      }
  
      private long[] readArr2(int N) throws Exception {
          long[] arr = new long[N];
          st = new StringTokenizer(infile.readLine());
          for (int i = 0; i < N; i++) {
              arr[i] = Long.parseLong(st.nextToken());
          }
          return arr;
      }
  
      private boolean isPrime(long n) {
          if (n < 2) {
              return false;
          }
          if (n == 2 || n == 3) {
              return true;
          }
          if (n % 2 == 0 || n % 3 == 0) {
              return false;
          }
          long sqrtN = (long) Math.sqrt(n) + 1;
          for (long i = 6L; i <= sqrtN; i += 6) {
              if (n % (i - 1) == 0 || n % (i + 1) == 0) {
                  return false;
              }
          }
          return true;
      }
  
      private long gcd(long a, long b) {
          if (a > b) {
              a = (a + b) - (b = a);
          }
          if (a == 0L) {
              return b;
          }
          return gcd(b % a, a);
      }
      
      private int gcd(int a, int b) {
          if (a > b) {
              a = (a + b) - (b = a);
          }
          if (a == 0) {
              return b;
          }
          return gcd(b % a, a);
      }
      
      private double pow(double x, long N) {
          double ans = 1.0;
          // 贡献的初始值为 x
          double x_contribute = x;
          // 在对 N 进行二进制拆分的同时计算答案
          while (N > 0) {
              if (N % 2 == 1) {
                  // 如果 N 二进制表示的最低位为 1，那么需要计入贡献
                  ans *= x_contribute;
              }
              // 将贡献不断地平方
              x_contribute *= x_contribute;
              // 舍弃 N 二进制表示的最低位，这样我们每次只要判断最低位即可
              N /= 2;
          }
          return ans;
      }
      
      long pow_mod(long x, long i, long mod){
  	    if(i == 0){
  	        return 1;
  	    }
  	    long temp = pow_mod(x, i >> 1, mod);
  	    temp = temp * temp % mod;
  	    if((i & 1 )!= 0){
  	        temp = temp * x % mod;
  	    }
  	    return temp;
  	}
      
  
  }
  