#pragma GCC optimize("O2")
#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const ll MXN = 3e3 + 10;
ll H, W, Par[MXN], Sz[MXN];
vector<ll> R[MXN], C[MXN];
string s[MXN];
ll f1, f2, f3;
ll Find(ll x){
    return (x == Par[x] ? x : Par[x] = Find(Par[x]));
}
bool Union(ll u, ll v){
    u = Find(u), v = Find(v);
    if(u == v) return 0;
    if(Sz[v] > Sz[u]) swap(u, v);
    Sz[u] += Sz[v], Par[v] = u;
    return 1;
}
void Build(){
    for(int i = 1; i <= H + W; i ++){
        Par[i] = i, Sz[i] = 1;
    }
    for(int i = 1; i <= H; i ++){
        for(int j = 1; j <= W; j ++){
            if(s[i][j] == '#') Union(i, j + H);
        }
    }
}
int main(){
    //ios::sync_with_stdio(0);cin.tie(0); cout.tie(0);
    cin >> H >> W;
    if(min(H, W) <= 2) return cout << 0, 0;
    for(int i = 1; i <= H; i ++){
        cin >> s[i];
        s[i] = "$" + s[i];
    }
    s[1][1] = s[1][W] = s[H][1] = s[H][W] = '#';
    Build();
    for(int i = 1; i <= H + W; i ++){
        if(Find(i) != i) continue;
        f1 += Union(i, 1);
    }


    Build();
    for(int i = 1; i <= H; i ++){
        f2 += Union(1, i);
    }



    Build();
    for(int j = 1; j <= W; j ++){
        f3 += Union(1, j + H);
    }
    for(int i = 1; i <= 100; i ++) Build();
    cout << min({f1, f2, f3}) << '\n';
    return 0;
}

#include<bits/stdc++.h>
using namespace std;
vector<string> unique(vector<string> vec){ //あんまり値渡しは良くない気がするけど一回ならいいか。
    //12文字ならKMPとかするまでもないね。
    sort(vec.begin(),vec.end(),[](auto const& l,auto const& r){
        if(l.size()==r.size())return l>r;
        return l.size()>r.size();
    });//文字列長い順にsort
    vector<pair<string,int>> cur;
    for(int i=0;i<vec.size();i++){
        string a=vec[i]; // 今から、aの判定をします
        bool f=false;
        for(int j=0;j<cur.size();j++){
            string b=cur[j].first; //bと比較します。
            for(int k=0;k+a.size()<=b.size();k++){
                if(b.substr(k,a.size())==a){
                    cur[j].second++;
                    f=true;
                }
            }
        }
        if(!f){
            cur.push_back({a,0});
        }
    }
    sort(cur.begin(),cur.end(),[](auto const& l,auto const& r){
        if(l.second==r.second){
            if(l.first.size()==r.first.size()){
                return l.first>r.first;
            }
            return l.first.size()>r.first.size();
        }
        return l.second>r.second;
    });
    vector<string> ret;
    for(int i=0;i<cur.size();i++){
        ret.push_back(cur[i].first);
    }
    return ret;
}
int main(){
    int n,m;
    cin>>n>>m;
    vector<string> v(m);
    for(int i=0;i<m;i++){
        cin>>v[i];
    }
    v=unique(v);
    m=v.size();
    vector<string> b(n,string(n,'.'));
    int cov=0;
    for(int i=0;i<m;i++){//iを埋め込む。
        bool ac=false;
        for(int j=0;j<n;j++){
            for(int k=0;k<n;k++){
                bool f=true;
                for(int l=0;l<v[i].size();l++){
                    if(b[j][(k+l)%n]!='.'&&b[j][(k+l)%n]!=v[i][l]){
                        f=false;
                        break;
                    }
                }
                if(f){
                    for(int l=0;l<v[i].size();l++){
                        b[j][(k+l)%n]=v[i][l];
                    }
                    ac=true;
                    cov++;
                    break;
                }
            }
            if(ac){
                break;
            }
        }
    }
    for(int i=0;i<n;i++){
        cout<<b[i]<<endl;
    }
//    cerr<<"Analytics:"<<endl<<" Actual M="<<m<<endl<<" Cover Piece="<<cov<<endl;
    return 0;
}