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: Accepted)

#include <bits/stdc++.h>
#define ll long long
#define INF 1000000005
#define MOD 1000000007
#define EPS 1e-10
#define rep(i,n) for(int i=0;i<(int)(n);++i)
#define rrep(i,n) for(int i=(int)(n)-1;i>=0;--i)
#define srep(i,s,t) for(int i=(int)(s);i<(int)(t);++i)
#define each(a,b) for(auto& (a): (b))
#define all(v) (v).begin(),(v).end()
#define len(v) (int)(v).size()
#define zip(v) sort(all(v)),v.erase(unique(all(v)),v.end())
#define cmx(x,y) x=max(x,y)
#define cmn(x,y) x=min(x,y)
#define fi first
#define se second
#define pb push_back
#define show(x) cout<<#x<<" = "<<(x)<<endl
#define sar(a,n) cout<<#a<<":";rep(pachico,n)cout<<" "<<a[pachico];cout<<endl

using namespace std;

template<typename S,typename T>auto&operator<<(ostream&o,pair<S,T>p){return o<<"{"<<p.fi<<","<<p.se<<"}";}
template<typename T>auto&operator<<(ostream&o,set<T>s){for(auto&e:s)o<<e<<" ";return o;}
template<typename S,typename T,typename U>
auto&operator<<(ostream&o,priority_queue<S,T,U>q){while(!q.empty())o<<q.top()<<" ",q.pop();return o;}
template<typename K,typename T>auto&operator<<(ostream&o,map<K,T>m){for(auto&e:m)o<<e<<" ";return o;}
template<typename T>auto&operator<<(ostream&o,vector<T>v){for(auto&e:v)o<<e<<" ";return o;}
void ashow(){cout<<endl;}template<typename T,typename...A>void ashow(T t,A...a){cout<<t<<" ";ashow(a...);}

typedef pair<int,int> P;
typedef pair<ll,ll> pll;
typedef vector<int> vi;
typedef vector<ll> vl;
typedef vector<vi> vvi;
typedef vector<vl> vvl;
typedef vector<P> vp;
typedef vector<double> vd;
typedef vector<string> vs;

const int MAX_N = 190;

template<class _Key, class _Tp, class _Hash> class UnorderedMapIterator;

template<class _Key, class _Tp, class _Hash = hash<_Key> >
class UnorderedMap
{
private:
    using iterator = UnorderedMapIterator<_Key, _Tp, _Hash>;
    using data_type = pair<_Key, _Tp>;
    using aligned_pointer = typename std::aligned_storage<sizeof(data_type), alignof(data_type)>::type;
    friend UnorderedMapIterator<_Key, _Tp, _Hash>;
    struct bucket {
        unsigned int _hash;
        int _dist;
        bool _last, _end;
        aligned_pointer _data_ptr;
        bucket() : _dist(-1), _last(false), _end(false){}
        inline void clear(){ _dist = -1; }
        inline void _delete(){ data_ptr()->~data_type(); }
        inline bool empty() const noexcept { return (_dist == -1); }
        inline data_type& data() noexcept {
            return *reinterpret_cast<data_type*>(&_data_ptr);
        }
        inline data_type* data_ptr() noexcept {
            return reinterpret_cast<data_type*>(&_data_ptr);
        }
        inline const _Key& get_key() noexcept { return data_ptr()->first; }
        inline void new_data(const data_type& data){
            new(&_data_ptr) data_type(data);
        }
        inline void _swap(unsigned int& hash, int& dist, data_type& _data){
            swap(_hash, hash), swap(_dist, dist), swap(data(), _data);
        }
    };
    inline static unsigned int ceilpow2(unsigned int u){
        --u, u |= u >> 1, u |= u >> 2, u |= u >> 4, u |= u >> 8;
        return (u | (u >> 16)) + 1;
    }
    inline static bucket *increment(bucket *cur) noexcept {
        for(++cur; !cur->_end; ++cur){
            if(!cur->empty()) break;
        }
        return cur;
    }
    inline bucket *next_bucket(bucket*& cur) const noexcept {
        return cur->_last ? _buckets : cur + 1;
    }
    inline unsigned int make_hash(const _Key& _key) const noexcept {
        return _key;
        // return rand1 ^ (_Hash()(_key) + rand2);
    }
    inline float load_factor() const noexcept {
        return (float)_data_count / _bucket_count;
    }
    // inline void make_random_number(){
    //     rand1 = random_device{}(), rand2 = 0x9e3779b9 + (rand1 << 6) + (rand1 >> 2);
    // }
    bucket *insert_impl(bucket *cur, unsigned int hash, int dist, data_type& data){
        bucket *ret = cur;
        bool flag = false;
        while(true){
            if(cur->empty()){
                cur->_hash = hash, cur->_dist = dist, cur->new_data(data);
                if(!flag) ret = cur, flag = true;
                break;
            }else if(dist > cur->_dist){
                cur->_swap(hash, dist, data);
                if(!flag) ret = cur, flag = true;
            }
            ++dist;
            cur = next_bucket(cur);
        }
        return ret;
    }
    bucket *_find(const _Key& _key, bool push=false){
        unsigned int hash = make_hash(_key);
        bucket *cur = _buckets + (hash & _mask);
        int dist = 0;
        while(dist <= cur->_dist){
            if(hash == cur->_hash && _key == cur->get_key()){
                return cur;
            }
            ++dist;
            cur = next_bucket(cur);
        }
        if(!push) return _buckets + _bucket_count;
        ++_data_count;
        if(rehash_check()){
            hash = make_hash(_key), cur = _buckets + (hash & _mask), dist = 0;
        }
        data_type tmp = data_type(_key, _Tp());
        return insert_impl(cur, hash, dist, tmp);
    }
    bucket *_insert(const _Key& _key, const _Tp& _value){
        ++_data_count;
        rehash_check();
        unsigned int hash = make_hash(_key);
        data_type tmp = data_type(_key, _value);
        return insert_impl(_buckets + (hash & _mask), hash, 0, tmp);
    }
    bucket *backward_shift(bucket *cur){
        cur->_delete();
        bucket *next = next_bucket(cur), *ret = cur;
        if(next->_dist < 1) return increment(cur);
        do {
            cur->_hash = next->_hash;
            cur->_dist = next->_dist - 1;
            cur->_data_ptr = next->_data_ptr;
            cur = next, next = next_bucket(cur);
        }while(next->_dist >= 1);
        cur->clear();
        return ret;
    }
    bucket *erase_impl(bucket *cur){
        assert(cur != _buckets + _bucket_count);
        --_data_count;
        cur->clear();
        return backward_shift(cur);
    }
    bucket *_erase(const _Key& _key){
        return erase_impl(_find(_key));
    }
    bool rehash_check(){
        if(load_factor() >= MAX_LOAD_FACTOR){
            rehash();
            return true;
        }else{
            return false;
        }
    }
    void move_data(data_type& data){
        unsigned int hash = make_hash(data.first);
        insert_impl(_buckets + (hash & _mask), hash, 0, data);
    }
    void rehash(){
        UnorderedMap new_unordered_map(_bucket_count * 2);
        new_unordered_map._data_count = _data_count;
        for(bucket *cur = _buckets; !cur->_end; ++cur){
            if(!cur->empty()){
                new_unordered_map.move_data(cur->data());
            }
        }
        swap(*this, new_unordered_map);
    }
    friend void swap(UnorderedMap& ump1, UnorderedMap& ump2){
        swap(ump1._bucket_count, ump2._bucket_count);
        swap(ump1._mask, ump2._mask);
        swap(ump1._data_count, ump2._data_count);
        // swap(ump1.rand1, ump2.rand1);
        // swap(ump1.rand2, ump2.rand2);
        swap(ump1._buckets, ump2._buckets);
    }

private:
    unsigned int _bucket_count, _mask, _data_count;
    // unsigned int rand1, rand2;
    bucket *_buckets;
public:
    static constexpr float MAX_LOAD_FACTOR = 0.5f;
    UnorderedMap(unsigned int bucket_size = 1u) noexcept
     : _bucket_count(ceilpow2(max(bucket_size, 1u))), _mask(_bucket_count - 1),
        _data_count(0u), _buckets(new bucket[_bucket_count + 1]){
        _buckets[_bucket_count - 1]._last = true, _buckets[_bucket_count]._end = true;
        // make_random_number();
    }
    ~UnorderedMap(){ delete[] _buckets; }
    friend ostream& operator<< (ostream& os, UnorderedMap& ump) noexcept {
        for(data_type& val : ump) os << '{' << val.first << ',' << val.second << "} ";
        return os;
    }
    _Tp& operator[](const _Key& _key) noexcept {
        return _find(_key, true)->data_ptr()->second;
    }
    const _Tp& at(const _Key& _key){
        bucket *res = _find(_key);
        if(res == _buckets + _bucket_count) __throw_out_of_range(__N("Unordered_Map::at"));
        return res->data_ptr()->second;
    }
    void clear(){
        UnorderedMap new_unordered_map(1u);
        swap(*this, new_unordered_map);
    }
    size_t size() const noexcept { return _data_count; }
    size_t bucket_count() const noexcept { return _bucket_count; }
    bool empty() const noexcept { return (_data_count == 0); }
    iterator begin() const noexcept { return _buckets->empty() ? iterator(increment(_buckets)) : iterator(_buckets); }
    iterator end() const noexcept { return iterator(_buckets + _bucket_count); }
    iterator find(const _Key& _key){ return iterator(_find(_key)); }
    iterator insert(const _Key& _key, const _Tp& _value){
        return iterator(_insert(_key, _value));
    }
    iterator erase(const _Key& _key){ return iterator(_erase(_key)); }
    iterator erase(const iterator& itr){ return iterator(erase_impl(itr.bucket_ptr)); }
};

template<class _Key, class _Tp, class _Hash>
class UnorderedMapIterator {
private:
    friend UnorderedMap<_Key, _Tp, _Hash>;
    typename UnorderedMap<_Key, _Tp, _Hash>::bucket *bucket_ptr;
    using iterator_category = forward_iterator_tag;
    using data_type = pair<_Key, _Tp>;
    using difference_type = std::ptrdiff_t;
    using pointer = pair<_Key, _Tp>*;
    using reference = pair<_Key, _Tp>&;

private:
    UnorderedMapIterator(typename UnorderedMap<_Key, _Tp, _Hash>::bucket *_bucket_ptr)
        noexcept : bucket_ptr(_bucket_ptr){}
public:
    UnorderedMapIterator() noexcept : bucket_ptr(){}
    UnorderedMapIterator(const UnorderedMapIterator& itr) noexcept : bucket_ptr(itr.bucket_ptr){}
    UnorderedMapIterator& operator=(const UnorderedMapIterator& itr)
        & noexcept { return bucket_ptr = itr.bucket_ptr, *this; }
    UnorderedMapIterator& operator=(const UnorderedMapIterator&& itr)
        & noexcept { return bucket_ptr = itr.bucket_ptr, *this; }
    reference operator*() const { return bucket_ptr->data(); }
    pointer operator->() const { return bucket_ptr->data_ptr(); }
    UnorderedMapIterator& operator++() noexcept {
        return bucket_ptr = UnorderedMap<_Key, _Tp, _Hash>::increment(bucket_ptr), *this;
    }
    UnorderedMapIterator operator++(int) const noexcept {
        return UnorderedMapIterator(UnorderedMap<_Key, _Tp, _Hash>::increment(this->bucket_ptr));
    }
    bool operator==(const UnorderedMapIterator& itr) const noexcept { return !(*this != itr); };
    bool operator!=(const UnorderedMapIterator& itr) const noexcept { return bucket_ptr != itr.bucket_ptr; }
};

struct RH {
    static int mod, mul[2];
    static vector<int> pm[2];
    vector<string> s; int rw, cl; vector<vector<int> > hs;
    void init(vector<string>& s){
        this->s = s; rw = (int)s.size(), cl = (int)s[0].size();
        hs.resize(rw+1,vector<int>(cl+1,0));
        if(pm[0].empty()) pm[0].push_back(1), pm[1].push_back(1);
        for(int j = 0; j < rw; j++){
            for(int k = 0; k < cl; k++){
                hs[j+1][k+1] = ((ll)hs[j+1][k]*mul[0]+s[j][k])%mod;
            }
        }
        for(int j = 0; j < rw; j++){
            for(int k = 0; k < cl; k++){
                hs[j+1][k+1] = ((ll)hs[j][k+1]*mul[1]+hs[j+1][k+1])%mod;
            }
        }
    }
    int comp(int u, int l, int d, int r, int id, const int mod){
        return (hs[d][r]-(ll)hs[d][l]*pm[0][r-l]
                -(ll)hs[u][r]*pm[1][d-u]
                +(ll)hs[u][l]*pm[0][r-l]%mod*pm[1][d-u])%mod;
    }
    int hash(int u,int l,int d,int r){ //文字列sのインデックス[u,d),[l,r)までの部分文字列のハッシュ値
        if(l>=r||u>=d) return 0;
        while((int)pm[0].size() <= r) pm[0].push_back((ll)pm[0].back()*mul[0]%mod);
        while((int)pm[1].size() <= d) pm[1].push_back((ll)pm[1].back()*mul[1]%mod);
        return (hs[d][r]-(ll)hs[d][l]*pm[0][r-l]
                -(ll)hs[u][r]*pm[1][d-u]
                +(ll)hs[u][l]*pm[0][r-l]%mod*pm[1][d-u])%mod;
    }
};
vector<int> RH::pm[2];
int RH::mod=1000000007, RH::mul[2]={10009,10039};

UnorderedMap<int,int> mp;
int imos[MAX_N][MAX_N];

int calc(int l1, int r1, int l2, int r2)
{
    return imos[r1][r2] - imos[r1][l2] - imos[l1][r2] + imos[l1][l2];
}

int rec(int l1, int r1, int l2, int r2, RH& rh)
{
    if(l1 >= r1 || l2 >= r2) return 0;
    int val = calc(l1, r1, l2, r2);
    if(val == 0 || val == (r1-l1)*(r2-l2)) return 0;
    int hs = rh.hash(l1, l2, r1, r2);
    auto it = mp.find(hs);
    if(it != mp.end()) return it->se;
    int res = INF;
    // yoko
    int l = l1, r = r1;
    while(r-l>1){
        int mid = (l+r)/2;
        int v1 = rec(l1, mid, l2, r2, rh), v2 = rec(mid, r1, l2, r2, rh);
        if(v1 < v2){
            cmn(res, v2 + 1);
            l = mid;
        }else if(v1 == v2){
            cmn(res, v1 + 1);
            break;
        }else{
            cmn(res, v1 + 1);
            r = mid;
        }
    }
    l = l2, r = r2;
    while(r-l>1){
        int mid = (l+r)/2;
        int v1 = rec(l1, r1, l2, mid, rh), v2 = rec(l1, r1, mid, r2, rh);
        if(v1 < v2){
            cmn(res, v2 + 1);
            l = mid;
        }else if(v1 == v2){
            cmn(res, v1 + 1);
            break;
        }else{
            cmn(res, v1 + 1);
            r = mid;
        }
    }
    mp[hs] = res;
    return res;
}

int main()
{
    cin.tie(0);
    ios::sync_with_stdio(false);
    int h, w;
    cin >> h >> w;
    vs s(h);
    rep(i,h){
        cin >> s[i];
        rep(j,len(s[i])){
            if(s[i][j] == '#'){
                imos[i+1][j+1]++;
            }
        }
    }
    rep(i,h+1){
        rep(j,w+1){
            imos[i][j+1] += imos[i][j];
        }
    }
    rep(j,w+1){
        rep(i,h+1){
            imos[i+1][j] += imos[i][j];
        }
    }
    // rep(i,h+1){
    //     rep(j,w+1){
    //         cout << imos[i][j];
    //     }
    //     cout << "\n";
    // }
    // show(calc(0,2,0,2));
    RH rh;
    rh.init(s);
    cout << rec(0, h, 0, w, rh) << "\n";
    return 0;
}