// Original C++ solution (slower):
#include<bits/stdc++.h>

using namespace std;

#define MAX_V 500

#define INF 1<<28



class bigint {

private:

  static const int BASE = 100000000, LEN = 40;

  bool negative;

  std::vector<int> a;

  bigint& normalize();

public:

  bigint(int x = 0);

  bigint(const std::string& s);

  bigint& operator = (const bigint& x);

  bigint& operator = (int x);

  bigint& operator = (const std::string& s);

  const bool operator < (const bigint& x) const;

  const bool operator > (const bigint& x) const;

  const bool operator <= (const bigint& x) const;

  const bool operator >= (const bigint& x) const;

  const bool operator != (const bigint& x) const;

  const bool operator == (const bigint& x) const;

  bigint operator -() const;

  bigint& operator += (const bigint& x);

  bigint& operator -= (const bigint& x);

  bigint& operator *= (const bigint& x);

  bigint& operator /= (const bigint& x);

  bigint& operator %= (const bigint& x);

  const bigint operator + (const bigint& x) const;

  const bigint operator - (const bigint& x) const;

  const bigint operator * (const bigint& x) const;

  const bigint operator / (const bigint& x) const;

  const bigint operator % (const bigint& x) const;

  friend std::pair<bigint,bigint> divmod(const bigint& lhs, const bigint& rhs);

  friend std::istream& operator >> (std::ostream& is, bigint& x); //??????????£?

  friend std::ostream& operator << (std::ostream& os, const bigint& x);

  friend const bigint abs(bigint x);

};

bigint& bigint::normalize() {

  int i = a.size()-1;

  while (i >= 0 && a[i] == 0) --i;

  a.resize(i+1);

  if (a.size() == 0) negative = false;

  return *this;

}

bigint::bigint(int x) : negative(x<0) {

  x = abs(x);

  for (; x > 0; x /= BASE) a.push_back(x % BASE);

}

bigint::bigint(const std::string& s): negative(false) {

  int p = 0;

  if (s[p] == '-') { ++p; negative = true; }

  else if (s[p] == '+') { ++p; }

  for (int i = s.size()-1, v = BASE; i >= p; --i, v*=10) {

    int x = s[i]-'0';

    if (x < 0 || 9 < x) {

      std::cerr<<"error: parse error:"<<s<<std::endl;

      exit(1);

    } 

    if (v == BASE) {

      v = 1;

      a.push_back(x);

    } else a.back() += (x)*v;

  }

  normalize();

}

bigint& bigint::operator = (const bigint& x) {

  negative = x.negative;

  a = x.a;

  return *this;

}

bigint& bigint::operator = (int x) { return *this = bigint(x); }

bigint& bigint::operator = (const std::string& s) { return *this = bigint(s); }

const bool bigint::operator < (const bigint& x) const {

  if (negative != x.negative) return negative < x.negative;

  if (a.size() != x.a.size()) return (a.size() < x.a.size())^negative;

  for(int i = a.size()-1; i >= 0; --i)

    if (a[i] != x.a[i]) return (a[i] < x.a[i])^negative;

  return false;

}

const bool bigint::operator > (const bigint& x) const { return x<(*this); }

const bool bigint::operator <= (const bigint& x) const { return !(x<(*this)); }

const bool bigint::operator >= (const bigint& x) const { return !((*this)<x); }

const bool bigint::operator != (const bigint& x) const { return (*this)<x || x<(*this); }

const bool bigint::operator == (const bigint& x) const { return !((*this)<x || x<(*this)); }

bigint bigint::operator -() const {

  bigint ret(*this);

  if (a.size()) ret.negative = !ret.negative;

  return ret;

}

bigint& bigint::operator += (const bigint& x) {

  if (negative != x.negative) return *this -= -x;

  if (a.size() < x.a.size()) a.resize(x.a.size());

  int tmp = 0;

  for (int i = 0; i < a.size(); ++i) {

    a[i] += (i<x.a.size()?x.a[i]:0) + tmp;

    tmp = a[i] / BASE;

    a[i] %= BASE;

  }

  if (tmp) a.push_back(1);

  return *this;

}

bigint& bigint::operator -= (const bigint& x) {

  if (negative != x.negative) return *this += -x;

  std::vector<int> b(x.a);

  if ((*this < x) ^ negative) {

    a.swap(b);

    negative = !negative;

  }

  for (int i = 0, tmp = 0; i < a.size(); ++i) {

    a[i] += BASE - (i<b.size()?b[i]:0) + tmp;

    tmp = a[i] / BASE - 1;

    a[i] %= BASE;

  }

  return this->normalize();

}

bigint& bigint::operator *= (const bigint& x) {

  negative ^= x.negative;

  std::vector<int> c(a.size()*x.a.size()+1);

  for (int i = 0; i < a.size(); ++i) {

    long long tmp = 0;

    for (int j = 0; j < x.a.size(); ++j) {

      long long v = (long long)a[i] * x.a[j] + c[i+j] + tmp;

      tmp = v / BASE;

      c[i+j] = (int)(v % BASE);

    }

    if (tmp) c[i+x.a.size()] += (int)tmp;

  }

  a.swap(c);

  return this->normalize();

}

bigint& bigint::operator /= (const bigint& x) {

  return *this = divmod(*this,x).first;

}

bigint& bigint::operator %= (const bigint& x) {

  return *this = divmod(*this,x).second;

}

const bigint bigint::operator + (const bigint& x) const {

  bigint res(*this); return res += x;

}

const bigint bigint::operator - (const bigint& x) const {

  bigint res(*this); return res -= x;

}

const bigint bigint::operator * (const bigint& x) const {

  bigint res(*this); return res *= x;

}

const bigint bigint::operator / (const bigint& x) const {

  bigint res(*this); return res /= x;

}

const bigint bigint::operator % (const bigint& x) const {

  bigint res(*this); return res %= x;

}

std::pair<bigint,bigint> divmod(const bigint& lhs, const bigint& rhs) {

  if (!rhs.a.size()) {

    std::cerr<<"error: division by zero"<<std::endl;

    exit(1);

  }

  bigint x(abs(rhs)), q, r;

  for (int i = lhs.a.size()-1; i >= 0; --i) {

    r = r * bigint::BASE + lhs.a[i];

    int head = 0, tail = bigint::BASE;

    if (r >= x) {

      while (head + 1 < tail) {

        int mid = (head + tail) / 2;

        if (x * bigint(mid) > r) tail = mid;

        else head = mid;

      }

      r -= x * head;

    }

    q.a.push_back(head);

  }

  reverse(q.a.begin(), q.a.end());

  bool neg = lhs.negative ^ lhs.negative;

  q.negative = neg; r.negative = neg;

  return std::make_pair(q.normalize(), r.normalize());

}

std::istream& operator >> (std::istream& is, bigint& x) {

  std::string tmp; is >> tmp;

  x = bigint(tmp);

  return is;

}

std::ostream& operator << (std::ostream& os, const bigint& x) {

  if (x.negative) os << '-';

  if (!x.a.size()) os << 0;

  else os << x.a.back();

  for (int i = x.a.size()-2; i >= 0; --i) {

    os.width(bigint::LEN);

    os.fill('0');

    os << x.a[i];

  }

  return os;

}

const bigint abs(bigint x) {

  x.negative = false;

  return x;

}



bigint base=bigint("100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000");

bigint inf=bigint("10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000");



typedef pair<bigint,int> P;



struct edge{

  int to,rev;

  bigint cap,cost;

  edge(){}

  edge(int to,bigint cap,int rev,bigint cost):to(to),cap(cap),rev(rev),cost(cost){}

};



int V;

vector<edge> G[MAX_V];

bigint h[MAX_V];

bigint dist[MAX_V];

int prevv[MAX_V],preve[MAX_V];



void add_edge(int from,int to,bigint cap,bigint cost){

  G[from].push_back(edge(to,cap,G[to].size(),cost));

  G[to].push_back(edge(from,0,G[from].size()-1,-cost));

}



bigint min_cost_flow(int s,int t,bigint f){

  bigint res=0;

  for(int i=0;i<V;i++) h[i]=0;

  while(f>0){

    priority_queue<P,vector<P>,greater<P> > que;

    for(int i=0;bigint(i)<V;i++) dist[i]=inf;

    dist[s]=bigint(0);

    que.push(P(0,s));

    while(!que.empty()){

      P p=que.top();que.pop();

      int v=p.second;

      if(dist[v]<p.first) continue;

      for(int i=0;i<(int)G[v].size();i++){

	edge &e=G[v][i];

	if(e.cap>0&&dist[e.to]>dist[v]+e.cost+h[v]-h[e.to]){

	  dist[e.to]=dist[v]+e.cost+h[v]-h[e.to];

	  prevv[e.to]=v;

	  preve[e.to]=i;

	  que.push(P(dist[e.to],e.to));

	}

      }

    }

    if(dist[t]==INF){

      return -1;

    }

    for(int v=0;bigint(v)<V;v++) h[v]+=dist[v];



    bigint d=f;

    for(int v=t;v!=s;v=prevv[v]){

      d=min(d,G[prevv[v]][preve[v]].cap);

    }

    f-=d;

    res+=d*h[t];

    for(int v=t;v!=s;v=prevv[v]){

      edge &e=G[prevv[v]][preve[v]];

      e.cap-=d;

      G[v][e.rev].cap+=d;

    }

  }

  return res;

}



signed main(){

  int n;

  cin>>n;

  string str[n],ans;

  for(int i=0;i<n;i++) cin>>str[i];

  V=n*2+2;

  int s=n*2,t=n*2+1;

  

  for(int i=0;i<n;i++){

    add_edge(s,i,1,0);

    add_edge(n+i,t,1,0);

  }

  bigint cost[52];

  for(int i=0;i<52;i++){

    cost[i]=1;

    for(int j=0;j<52-i;j++){

      cost[i]*=52;

    }

  }

  map<char,int> c;

  for(char i='A';i<='Z';i++) c[i]=i-'A';

  for(char i='a';i<='z';i++) c[i]=i-'a'+26;

  

  for(int i=0;i<n;i++)

    for(int j=0;j<n;j++)

      add_edge(i,n+j,1,base-cost[c[str[i][j]]]);



  min_cost_flow(s,t,bigint(n));

  



  for(int i=0;i<n;i++){

    for(int j=0;j<(int)G[i].size();j++){

      if(G[i][j].cap>bigint(0)) continue;

      ans+=str[i][G[i][j].to-n];

    }

  }

  sort(ans.begin(),ans.end());

  cout<<ans<<endl;

  return 0;

}

// Optimized solution to the same problem (faster):
#include<bits/stdc++.h>

using namespace std;

using Int = long long;

template<typename T1,typename T2> inline void chmin(T1 &a,T2 b){if(a>b) a=b;}

template<typename T1,typename T2> inline void chmax(T1 &a,T2 b){if(a<b) a=b;}



//BEGIN CUT HERE

template<typename TF,typename TC>

struct PrimalDual{

  struct edge{

    int to;

    TF cap;

    TC cost;

    int rev;

    edge(){}

    edge(int to,TF cap,TC cost,int rev):to(to),cap(cap),cost(cost),rev(rev){}

  };



  static const TC INF;

  vector<vector<edge>> G;

  vector<TC> h,dist;

  vector<int> prevv,preve;



  PrimalDual(){}

  PrimalDual(int n):G(n),h(n),dist(n),prevv(n),preve(n){}



  void add_edge(int u,int v,TF cap,TC cost){

    G[u].emplace_back(v,cap,cost,G[v].size());

    G[v].emplace_back(u,0,-cost,G[u].size()-1);

  }



  void dijkstra(int s){

    struct P{

      TC first;

      int second;

      P(TC first,int second):first(first),second(second){}

      bool operator<(const P&a) const{return a.first<first;}

    };

    priority_queue<P> que;

    fill(dist.begin(),dist.end(),INF);



    dist[s]=0;

    que.emplace(dist[s],s);

    while(!que.empty()){

      P p=que.top();que.pop();

      int v=p.second;

      if(dist[v]<p.first) continue;

      for(int i=0;i<(int)G[v].size();i++){

        edge &e=G[v][i];

        if(e.cap==0) continue;

        if(dist[v]+e.cost+h[v]-h[e.to]<dist[e.to]){

          dist[e.to]=dist[v]+e.cost+h[v]-h[e.to];

          prevv[e.to]=v;

          preve[e.to]=i;

          que.emplace(dist[e.to],e.to);

        }

      }

    }

  }



  TC flow(int s,int t,TF f,int &ok){

    TC res=0;

    fill(h.begin(),h.end(),0);

    while(f>0){

      dijkstra(s);

      if(dist[t]==INF){

        ok=0;

        return res;

      }



      for(int v=0;v<(int)h.size();v++)

        if(dist[v]<INF) h[v]=h[v]+dist[v];



      TF d=f;

      for(int v=t;v!=s;v=prevv[v])

        d=min(d,G[prevv[v]][preve[v]].cap);



      f-=d;

      res=res+h[t]*d;

      for(int v=t;v!=s;v=prevv[v]){

        edge &e=G[prevv[v]][preve[v]];

        e.cap-=d;

        G[v][e.rev].cap+=d;

      }

    }

    ok=1;

    return res;

  }

};

template<typename TF, typename TC> const TC PrimalDual<TF, TC>::INF = numeric_limits<TC>::max()/2;

//END CUT HERE

//INSERT ABOVE HERE

int GRL_6_B(){

  cin.tie(0);

  ios::sync_with_stdio(0);

  int v,e,f;

  cin>>v>>e>>f;



  PrimalDual<int, int> pd(v);

  for(int i=0;i<e;i++){

    int u,v,c,d;

    cin>>u>>v>>c>>d;

    pd.add_edge(u,v,c,d);

  }

  int ok=0;

  int res=pd.flow(0,v-1,f,ok);

  cout<<(ok?res:-1)<<endl;

  return 0;

}

/*

  verified on 2019/07/05

  http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=GRL_6_B&lang=jp

*/



signed SPOJ_GREED(){

  cin.tie(0);

  ios::sync_with_stdio(0);

  auto solve=

    [](){

      int n;

      cin>>n;

      vector<int> cnt(n,0);

      for(int i=0;i<n;i++){

        int x;

        cin>>x;

        cnt[x-1]++;

      }

      using ll = long long;

      const ll INF = 1<<28;

      int S=n,T=n+1;

      PrimalDual<ll, ll> G(n+2);



      int m;

      cin>>m;

      for(int i=0;i<m;i++){

        int x,y;

        cin>>x>>y;

        x--;y--;

        G.add_edge(x,y,INF,1);

        G.add_edge(y,x,INF,1);

      }



      for(int i=0;i<n;i++){

        G.add_edge(S,i,cnt[i],0);

        G.add_edge(i,T,1,0);

      }



      int ok=0;

      ll res=G.flow(S,T,n,ok);

      assert(ok);

      cout<<res<<endl;

      return 0;



    };

  int t;

  cin>>t;

  while(t--) solve();

  return 0;

}

/*

  verified on 2019/07/05

  https://www.spoj.com/problems/GREED/

*/



signed geocon2013_B(){

  using D = double;



  int n;

  cin>>n;

  vector<D> xs(n),ys(n);

  for(int i=0;i<n;i++) cin>>xs[i]>>ys[i];



  vector<int> pos,neg;

  for(int i=0;i<n;i++){

    if(xs[i]>0) pos.emplace_back(i);

    if(xs[i]<0) neg.emplace_back(i);

  }



  int f=max(pos.size(),neg.size());

  if(f==0){

    cout<<0<<endl;

    return 0;

  }



  PrimalDual<int, D> G(n+3);

  int S=n,T=n+1,U=n+2;

  for(int z:pos) G.add_edge(S,z,1,0);

  for(int z:neg) G.add_edge(z,T,1,0);



  int dif=pos.size()-neg.size();

  if(dif>0){

    G.add_edge(U,T,dif,0);

    for(int p:pos)

      G.add_edge(p,U,1,abs(xs[p]));

  }

  if(dif<0){

    G.add_edge(S,U,-dif,0);

    for(int q:neg)

      G.add_edge(U,q,1,abs(xs[q]));

  }



  for(int p:pos)

    for(int q:neg)

      G.add_edge(p,q,1,

                 min(hypot(xs[p]+xs[q],ys[p]-ys[q]),abs(xs[p])+abs(xs[q])));



  int ok=0;

  D ans=G.flow(S,T,f,ok);

  assert(ok);

  cout<<fixed<<setprecision(12)<<ans<<endl;

  return 0;

}

/*

  verified on 2019/07/05

  https://atcoder.jp/contests/geocon2013/tasks/geocon2013_b

*/



const int MAX = 52;

struct ARR{

  array<int, MAX> val;

  ARR(){fill(val.begin(),val.end(),0);}

  ARR(int x){fill(val.begin(),val.end(),x);}

  int& operator[](int k){return val[k];};

  int operator[](int k)const{return val[k];};

  ARR operator+(const ARR &oth) const{

    ARR res;

    for(int i=0;i<MAX;i++)

      res[i]=val[i]+oth[i];

    return res;

  }

  ARR operator-(const ARR &oth) const{

    ARR res;

    for(int i=0;i<MAX;i++)

      res[i]=val[i]-oth[i];

    return res;

  }

  ARR operator-() const{

    ARR res;

    for(int i=0;i<MAX;i++)

      res[i]=-val[i];

    return res;

  }

  ARR operator*(const int &k) const{

    ARR res;

    for(int i=0;i<MAX;i++)

      res[i]=val[i]*k;

    return res;

  }

  ARR operator/(const int &k) const{

    ARR res;

    for(int i=0;i<MAX;i++)

      res[i]=val[i]/k;

    return res;

  }

  bool operator< (const ARR &oth) const{

    return val< oth.val;

  }

  bool operator==(const ARR &oth) const{

    return val==oth.val;

  }

};



namespace std {

  template<> class numeric_limits<ARR> {

  public:

    static ARR max() {return ARR(numeric_limits<int>::max());};

  };

}



signed AOJ_2679(){

  int n;

  cin>>n;

  vector<string> vs(n);

  for(int i=0;i<n;i++) cin>>vs[i];



  auto enc=

    [&](char c){

      if(isupper(c)) return c-'A';

      return 26+c-'a';

    };

  auto dec=

    [&](int d){

      if(d<26) return 'A'+d;

      return 'a'+d-26;

    };



  int S=n*2,T=n*2+1;

  PrimalDual<int, ARR> G(n*2+2);

  for(int i=0;i<n;i++){

    G.add_edge(S,i,1,ARR());

    G.add_edge(n+i,T,1,ARR());

  }



  const int INF = 1e5;

  for(int i=0;i<n;i++){

    for(int j=0;j<n;j++){

      ARR cost(INF);

      cost[enc(vs[i][j])]=INF-1;

      G.add_edge(i,n+j,1,cost);

    }

  }



  int ok;

  auto res=G.flow(S,T,n,ok);

  assert(ok);



  string ans;

  for(int i=0;i<MAX;i++)

    for(int j=0;j<n*INF-res[i];j++)

      ans+=dec(i);

  cout<<ans<<endl;



  return 0;

}

/*

  verified on 2019/07/05

  http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=2679

*/



signed main(){

  //GRL_6_B();

  //SPOJ_GREED();

  //geocon2013_B();

  AOJ_2679();

  return 0;

}




// Fastest known solution for this problem: