Problem:
Score : 1000 points

Problem Statement

Note the unusual memory limit.

For a rectangular grid where each square is painted white or black, we define its complexity as follows:

If all the squares are black or all the squares are white, the complexity is 0.

Otherwise, divide the grid into two subgrids by a line parallel to one of the sides of the grid, and let c_1 and c_2 be the complexities of the subgrids. There can be multiple ways to perform the division, and let m be the minimum value of \max(c_1, c_2) in those divisions. The complexity of the grid is m+1.

You are given a grid with H horizontal rows and W vertical columns where each square is painted white or black.
HW characters from A_{11} to A_{HW} represent the colors of the squares.
A_{ij} is # if the square at the i-th row from the top and the j-th column from the left is black, and A_{ij} is . if that square is white.

Find the complexity of the given grid.

Constraints

1 \leq H,W \leq 185

A_{ij} is # or ..

Input

Input is given from Standard Input in the following format:

H W
A_{11}A_{12}...A_{1W}
:
A_{H1}A_{H2}...A_{HW}

Output

Print the complexity of the given grid.

Sample Input 1

3 3
...
.##
.##

Sample Output 1

2

Let us divide the grid by the boundary line between the first and second columns.
The subgrid consisting of the first column has the complexity of 0, and the subgrid consisting of the second and third columns has the complexity of 1, so the whole grid has the complexity of at most 2.

Sample Input 2

6 7
.####.#
#....#.
#....#.
#....#.
.####.#
#....##

Sample Output 2

4

Solution in C++14 (GCC 5.4.1) (judge verdict: Wrong Answer)

// includes {{{
#include<iostream>
#include<iomanip>
#include<algorithm>
#include<vector>
#include<stack>
#include<queue>
#include<map>
#include<set>
#include<tuple>
#include<cmath>
#include<random>
#include<cassert>
#include<bitset>
#include<cstdlib>
// #include<deque>
// #include<multiset>
#include<cstring>
// #include<bits/stdc++.h>
// }}}
using namespace std;
using ll = long long;

string s[185];
const int C = 16;

int dp[2][185 + 1][185 + 1][185 + 1];
int h, w;

// accum2D range2D range2Dloop {{{
#include <type_traits>
using loop2d_int = long long;
template < class T >
void accum2D(T &val, int h, int w) {
  for(int i = 1; i < h; i++)
    for(int j = 0; j < w; j++) val[i][j] += val[i - 1][j];
  for(int i = 0; i < h; i++)
    for(int j = 1; j < w; j++) val[i][j] += val[i][j - 1];
}

template < class T,
         class V = typename std::remove_reference< decltype(T()[0][0]) >::type >
         V range2D(T &val, int y1, int x1, int y2, int x2, int h, int w) {
           if(y2 >= h) y2 = h - 1;
           if(x2 >= w) x2 = w - 1;
           if(y1 > y2 || x1 > x2) return static_cast< V >(0);
           V res(val[y2][x2]);
           if(y1 - 1 >= 0) res -= val[y1 - 1][x2];
           if(x1 - 1 >= 0) res -= val[y2][x1 - 1];
           if(y1 - 1 >= 0 && x1 - 1 >= 0) res += val[y1 - 1][x1 - 1];
           return res;
         }

template < class T,
         class V = typename std::remove_reference< decltype(T()[0][0]) >::type >
         auto range2Dloop(T &val, loop2d_int ty1, loop2d_int tx1, loop2d_int y2, loop2d_int x2,
             int h, int w) {
           if(ty1 > y2 || tx1 > x2) return static_cast< V >(0);

           loop2d_int y1 = ty1 % h;
           if(y1 < 0) y1 += h;
           loop2d_int x1 = tx1 % w;
           if(x1 < 0) x1 += w;

           y2 += y1 - ty1;
           x2 += x1 - tx1;

           loop2d_int gy = y2 / h;
           loop2d_int gx = x2 / w;
           y2 %= h;
           x2 %= w;

           V res(0);
           if(gy == 0 && gx == 0) {
             res += range2D< T >(val, y1, x1, y2, x2, h, w);
           } else if(gy == 0) {
             res += range2D< T >(val, y1, x1, y2, w - 1, h, w);
             res += range2D< T >(val, y1, 0, y2, x2, h, w);
             res += range2D< T >(val, y1, 0, y2, w - 1, h, w) * (gx - 1);
           } else if(gx == 0) {
             res += range2D< T >(val, y1, x1, h - 1, x2, h, w);
             res += range2D< T >(val, 0, x1, y2, x2, h, w);
             res += range2D< T >(val, 0, x1, h - 1, x2, h, w) * (gy - 1);
           } else {
             res += range2D< T >(val, y1, x1, h - 1, w - 1, h, w);                     // UL
             res += range2D< T >(val, 0, x1, y2, w - 1, h, w);                         // DL
             res += range2D< T >(val, y1, 0, h - 1, x2, h, w);                         // UR
             res += range2D< T >(val, 0, 0, y2, x2, h, w);                             // DR
             res += range2D< T >(val, y1, 0, h - 1, w - 1, h, w) * (gx - 1);           // U
             res += range2D< T >(val, 0, 0, y2, w - 1, h, w) * (gx - 1);               // D
             res += range2D< T >(val, 0, x1, h - 1, w - 1, h, w) * (gy - 1);           // L
             res += range2D< T >(val, 0, x1, h - 1, x2, h, w) * (gy - 1);              // R
             res += range2D< T >(val, 0, 0, h - 1, w - 1, h, w) * (gy - 1) * (gx - 1); // C
           }
           return res;
         }
// }}}

template <class T, class U> inline void smax(T &a, U b) { a = a > b ? a : b; }
template <class T, class U> inline void smin(T &a, U b) { a = a < b ? a : b; }

int main() {
  std::ios::sync_with_stdio(false), std::cin.tie(0);
  cin >> h >> w;
  vector<vector<int>> f(h, vector<int>(w, 0));
  for(int i = 0; i < h; i++) {
    cin >> s[i];
    for(int j = 0; j < w; j++) if(s[i][j] == '.') f[i][j] = 1;
  }
  accum2D(f, h, w);

  auto isMonotone = [&] (int r1, int c1, int r2, int c2) {
    int x = range2D(f, r1, c1, r2, c2, h, w);
    return x == (r2 - r1 + 1) * (c2 - c1 + 1) || x == 0;
  };

  // i = 0 : 累積和を使っていい感じに求める
  for(int rr = 0; rr < h; rr++) for(int r1 = 0; r1 + rr < h; r1++) {
    int r2 = r1 + rr;
    int c1 = 0, c2 = 0;
    for(;c2 < w; ) {
      while(c2 < w && isMonotone(r1, c1, r2, c2)) c2++;
      if(c1 == c2) {
        dp[0][r1][c1][r2] = c2-1;
        c1++, c2++;
      } else {
        for(;c1 < c2; c1++) {
          dp[0][r1][c1][r2] = c2-1;
        }
      }
    }
  }
  for(int cc = 0; cc < w; cc++) for(int c1 = 0; c1 + cc < w; c1++) {
    int c2 = c1 + cc;
    int r1 = 0, r2 = 0;
    for(;r2 < h; ) {
      while(r2 < h && isMonotone(r1, c1, r2, c2)) r2++;
      if(r1 == r2) {
        dp[0][r1][c1][c2] = r2-1;
        r1++, r2++;
      } else {
        for(;r1 < r2; r1++) {
          dp[1][r1][c1][c2] = r2-1;
        }
      }
    }
  }


  int i = 0;
  while(dp[0][0][0][h-1] < w - 1) {
    assert(i < C - 1);
    i++;
    // for(int r1 = 0; r1 < h; r1++) for(int c1 = 0; c1 < w; c1++) {
    //   for(int r2 = r1; r2 < h; r2++) dp[0][r1][c1][r2][i] = dp[0][r1][c1][r2][i-1], dump(i, r1, c1, r2, dp[0][r1][c1][r2][i-1]);
    //   for(int c2 = c1; c2 < w; c2++) dp[1][r1][c1][c2][i] = dp[1][r1][c1][c2][i-1], dump(i, r1, c1, c2, dp[1][r1][c1][c2][i-1]);
    // }
    for(int r1 = 0; r1 < h; r1++) for(int c1 = 0; c1 < w; c1++) {
      // 伸ばしたい方向と垂直に切る場合
      for(int r2 = r1; r2 < h; r2++) {
        // int &d = dp[0][r1][c1][r2][i];
        // int c2 = dp[0][r1][c1][r2][i-1] + 1;
        // if(c2 == w) d = w - 1;
        // else smax(d, dp[0][r1][c2][r2][i-1]);
        // 次元を一個減らしても矛盾は生じない
        int &d = dp[0][r1][c1][r2];
        int c2 = dp[0][r1][c1][r2] + 1;
        if(c2 == w) d = w - 1;
        else smax(d, dp[0][r1][c2][r2]);
      }
      for(int c2 = c1; c2 < w; c2++) {
        int &d = dp[1][r1][c1][c2];
        int r2 = dp[1][r1][c1][c2] + 1;
        if(r2 == h) d = h - 1;
        else smax(d, dp[1][r2][c1][c2]);
      }
      // 伸ばしたい方向と水平に切る場合
      {
        int c2 = w - 1, r2 = r1;
        for(;c2 >= c1; c2--) {
          for(;r2 < h && r2 <= dp[1][r1][c1][c2]; r2++) {
            smax(dp[0][r1][c1][r2], c2);
          }
        }
      }
      {
        int c2 = c1, r2 = h - 1;
        for(;r2 >= r1; r2--) {
          for(;c2 < w && c2 <= dp[0][r1][c1][r2]; c2++) {
            smax(dp[1][r1][c1][c2], r2);
          }
        }
      }
    }
  }

  cout << i << endl;

  return 0;
}