Problem:
C - Decoding Ancient Messages
Problem Statement
Professor Y's work is to dig up ancient artifacts. Recently, he found a lot of strange stone plates, each of which has $N^2$ characters arranged in an $N \times N$ matrix. Further research revealed that each plate represents a message of length $N$. Also, the procedure to decode the message from a plate was turned out to be the following:
Select $N$ characters from the plate one by one so that any two characters are neither in the same row nor in the same column.
Create a string by concatenating the selected characters.
Among all possible strings obtained by the above steps, find the lexicographically smallest one. It is the message represented by this plate.
NOTE: The order of the characters is defined as the same as the order of their ASCII values (that is, $\mathtt{A} \lt \mathtt{B} \lt \cdots \lt \mathtt{Z} \lt \mathtt{a} \lt \mathtt{b} \lt \cdots \lt \mathtt{z}$).
After struggling with the plates, Professor Y gave up decoding messages by hand. You, a great programmer and Y's old friend, was asked for a help. Your task is to write a program to decode the messages hidden in the plates.
Input
The input is formated as follows:
$N$
$c_{11} c_{12} \cdots c_{1N}$
$c_{21} c_{22} \cdots c_{2N}$
:
:
$c_{N1} c_{N2} \cdots c_{NN}$
The first line contains an integer $N$ ($1 \le N \le 50$). Each of the subsequent $N$ lines contains a string of $N$ characters. Each character in the string is an uppercase or lowercase English letter (
A
-
Z
,
a
-
z
).
Output
Print the message represented by the plate in a line.
Sample Input 1
3
aab
czc
baa
Output for the Sample Input 1
aac
Sample Input 2
36
iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii
iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii
iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii
iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii
iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii
iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii
iiiiiiiiiiQiiiiiiiiiiiiiiiQiiiiiiiii
iiiiiiiiiiQQQiiiiiiiiiiQQQQiiiiiiiii
iiiiiiiiiiQQQQQiiiiiiiQQQQiiiiiiiiii
iiiiiiiiiiiQQQQQQQQQQQQQQQiiiiiiiiii
iiiiiiiiiiiQQQQQQQQQQQQQQQiiiiiiiiii
iiiiiiiiiiiQQQQQQQQQQQQQQiiiiiiiiiii
iiiiiiiiiiiiQQQQQQQQQQQQQiiiiiiiiiii
iiiiiiiiiiiQQQQQQQQQQQQQQQiiiiiiiiii
iiiiiiiiiiQQQQQQQQQQQQQQQQQiiiiiiiii
iiiiiiQiiiQQQQQQQQQQQQQQQQQiiiQiiiii
iiiiiiQQiQQQQQQQQQQQQQQQQQQiiQQiiiii
iiiiiiiQQQQQQQQQQQQQQQQQQQQiQQiiiiii
iiiiiiiiiQQQQQQQQQQQQQQQQQQQQiiiiiii
iiiiiiiiQQQQQiiQQQQQQQQiiQQQQQiiiiii
iiiiiiQQQQQQiiiiQQQQQiiiiQQQQQQiiiii
iiiiiQQQQQQQQiiiQQQQQiiQQQQQQQQiQiii
iiiQQQQQQQiiQiiiQQQQQiiQiiQQQQQQQQii
iQQQQQQQQQiiiiiQQQQQQQiiiiiiQQQQQQQi
iiQQQQQQQiiiiiiQQQQQQQiiiiiiiiQQQiii
iQQQQiiiiiiiiiQQQQQQQQQiiiiiiiiQQQii
iiiiiiiiiiiiiiQQQQQQQQQiiiiiiiiiiiii
iiiiiiiiiiiiiQQQQQQQQQQiiiiiiiiiiiii
iiiiiiiiiiiiiQQQQQQQQQQiiiiiiiiiiiii
iiiiiiiiiiiiiiQQQQQQQQiiiiiiiiiiiiii
iiiiiiiiiiiiiiQQQQQQQQiiiiiiiiiiiiii
iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii
iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii
iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii
iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii
iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii
Output for th
...(truncated)

Submitted solutions:

code_snippet: #include <cstdio>
#include <cstring>
#include <queue>

using namespace std;

vector<int> operator + (const vector<int> &A, const vector<int> &B)
{
    vector<int> v(52);
    for (int i = 0; i < 52; ++i)
        v[i] = A[i] + B[i];
    return v;
}

vector<int> operator - (const vector<int> &A)
{
    vector<int> v(52);
    for (int i = 0; i < 52; ++i)
        v[i] = -A[i];
    return v;
}

vector<int> operator * (const int &x, const vector<int> &A)
{
    vector<int> v(52);
    for (int i = 0; i < 52; ++i)
        v[i] = x * A[i];
    return v;
}

const vector<int> INF(52, 10000);

const int N = 50 + 50 + 2, M = (50 * 50 + 50 + 50) * 2;

int head[N];
struct Edge {
    int nxt, to, cow;
    vector<int> cost;
    Edge() {}
    Edge(int _nxt, int _to, int _cow, vector<int> _cost) {
        nxt = _nxt; to = _to; cow = _cow; cost = _cost;
    }
} ed[M];
int ecnt, mx_flow;
vector<int> mi_cost(52, 0);

void init() {
    memset(head, -1, sizeof(head));
}

vector<int> c2v(char c)
{
    vector<int> v(52, 0);
    if (c >= 'A' && c <= 'Z')
        --v[c - 'A'];
    else
        --v[26 + (c - 'a')];
    return v;
}

void addedge(int u, int v, int cow, vector<int> cost) {
    ed[ecnt] = Edge(head[u], v, cow, cost);
    head[u] = ecnt++;
    ed[ecnt] = Edge(head[v], u, 0, -cost);
    head[v] = ecnt++;
}

queue <int> Q;
int pre[N], inq[N];
vector<int> dis[N];
bool Spfa(int S, int T) {
    for (int i = 0; i < N; ++i)
        dis[i] = INF;
    dis[S] = vector<int>(52, 0);
    Q.push(S);
    while(!Q.empty()) {
        int u = Q.front(); Q.pop();
        inq[u] = 0;
        for(int e = head[u]; ~e; e = ed[e].nxt) {
            if(!ed[e].cow) continue;
            int v = ed[e].to;
            if(dis[v] > dis[u] + ed[e].cost) {
                dis[v] = dis[u] + ed[e].cost;
                pre[v] = e;
                if(!inq[v]) {
                    inq[v] = 1;
                    Q.push(v);
                }
            }
        }
    }
    return dis[T] != INF;
}

void End(int S, int T) {
    int flow = 1e9;
    for(int u = T; u != S; u = ed[pre[u] ^ 1].to) {
        flow = min(flow, ed[pre[u]].cow);
    }
    for(int u = T; u != S; u = ed[pre[u] ^ 1].to) {
        ed[pre[u]].cow -= flow;
        ed[pre[u] ^ 1].cow += flow;
        mi_cost = mi_cost + flow * ed[pre[u]].cost;
    }
    mx_flow += flow;
}

char s[50][51];

int main()
{
    int n;
    scanf("%d", &n);
    for (int i = 0; i < n; ++i)
        scanf("%s", s[i]);
    init();
    for (int i = 0; i < n; ++i)
        for (int j = 0; j < n; ++j)
            addedge(i, j + n, 1, c2v(s[i][j]));
    for (int i = 0; i < n; ++i)
    {
        addedge(n + n, i, 1, vector<int>(52, 0));
        addedge(i + n, n + n + 1, 1, vector<int>(52, 0));
    }
    while (Spfa(n + n, n + n + 1))
        End(n + n, n + n + 1);
    for (int i = 0; i < 26; ++i)
        while (mi_cost[i] < 0)
        {
            putchar('A' + i);
            mi_cost[i]++;
        }
    for (int i = 26; i < 52; ++i)
        while (mi_cost[i] < 0)
        {
            putchar('a' + (i - 26));
            mi_cost[i]++;
        }
    puts("");
    return 0;
}
accuracy: 1
time_ms: 10
memory_kb: 4296
final_rank: 2

code_snippet: #include <vector>
#include <iostream>
#include <algorithm>
using namespace std;

const int INF = 1012345678;

string solve(int N, const vector<vector<int> >& A) {
	// after v1[id] += x, is v1 < v2 ?
	auto compare = [&](const vector<int>& v1, const vector<int>& v2, int id, int x) -> bool {
		for (int i = 0; i < id; i++) {
			if (v1[i] != v2[i]) {
				return v1[i] < v2[i];
			}
		}
		if ((v1[id] != INF ? v1[id] + x : INF) != v2[id]) {
			return v1[id] + x < v2[id];
		}
		for (int i = id + 1; i < 52; i++) {
			if (v1[i] != v2[i]) {
				return v1[i] < v2[i];
			}
		}
		return false;
	};
	vector<vector<int> > f1(N, vector<int>(N, 0));
	vector<int> f2(N, 0), f3(N, 0);
	vector<int> answer(52, 0);
	for (int i = 0; i < N; i++) {
		vector<vector<int> > d(N * 2 + 2, vector<int>(52, INF));
		vector<int> pre(N * 2 + 2, -1);
		d[N * 2] = vector<int>(52, 0);
		int loops = 0;
		while (true) {
			loops += 1;
			vector<vector<int> > nd = d;
			for (int j = 0; j < N; j++) {
				for (int k = 0; k < N; k++) {
					if (f1[j][k] == 0) {
						if (compare(d[j], nd[k + N], A[j][k], -1)) {
							nd[k + N] = d[j];
							nd[k + N][A[j][k]] -= 1;
							pre[k + N] = j;
						}
					}
					if (f1[j][k] == 1) {
						if (compare(d[k + N], nd[j], A[j][k], +1)) {
							nd[j] = d[k + N];
							nd[j][A[j][k]] += 1;
							pre[j] = k + N;
						}
					}
				}
			}
			for (int j = 0; j < N; j++) {
				if (f2[j] == 0) {
					if (compare(d[N * 2], nd[j], 0, 0)) {
						nd[j] = d[N * 2];
						pre[j] = N * 2;
					}
				}
				if (f2[j] == 1) {
					if (compare(d[j], nd[N * 2], 0, 0)) {
						nd[N * 2] = d[j];
						pre[N * 2] = j;
					}
				}
				if (f3[j] == 0) {
					if (compare(d[j + N], nd[N * 2 + 1], 0, 0)) {
						nd[N * 2 + 1] = d[j + N];
						pre[N * 2 + 1] = j + N;
					}
				}
				if (f3[j] == 1) {
					if (compare(d[N * 2 + 1], nd[j + N], 0, 0)) {
						nd[j + N] = d[N * 2 + 1];
						pre[j + N] = N * 2 + 1;
					}
				}
			}
			if (d == nd) {
				break;
			}
			d = nd;
		}
		int u = N * 2 + 1;
		while (u != N * 2) {
			int va = min(pre[u], u), vb = max(pre[u], u);
			if (vb == N * 2) {
				f2[va] ^= 1;
			}
			else if (vb == N * 2 + 1) {
				f3[va - N] ^= 1;
			}
			else {
				f1[va][vb - N] ^= 1;
			}
			u = pre[u];
		}
		for (int j = 0; j < 52; j++) {
			answer[j] += d[N * 2 + 1][j];
		}
	}
	string final_answer;
	for (int i = 0; i < 52; i++) {
		for (int j = 0; j < -answer[i]; j++) {
			final_answer += (i < 26 ? char('A' + i) : char('a' + (i - 26)));
		}
	}
	return final_answer;
}

int main() {
	int N;
	cin >> N;
	vector<vector<int> > A(N, vector<int>(N));
	for (int i = 0; i < N; i++) {
		string s;
		cin >> s;
		for (int j = 0; j < N; j++) {
			if ('A' <= s[j] && s[j] <= 'Z') {
				A[i][j] = int(s[j] - 'A');
			}
			else {
				A[i][j] = int(s[j] - 'a') + 26;
			}
		}
	}
	string answer = solve(N, A);
	cout << answer << endl;
	return 0;
}
accuracy: 1
time_ms: 20
memory_kb: 3496
final_rank: 1

code_snippet: #include<bits/stdc++.h>
using namespace std;
#define rep(i,n) for(int i=0;i<(int)(n);i++)
#define MP make_pair
typedef long long ll;

//std::priority_queue<T,std::vector<T>,decltype(comp)> Q(comp)


bool comp(vector<int>&a,vector<int>&b){ // a< b
    for(int i=0;i<=51;i++){
        if(a[i]<b[i])return 0;
        if(a[i]>b[i])return 1;
    }
    return 0;
}

vector<int> neg(vector<int>a){
    vector<int> p;
    for(auto x:a){
        p.push_back(-x);
    }
    return p;
}
vector<int> add(vector<int>&a,vector<int>&b){
    vector<int> c;
    rep(i,a.size()){
        c.push_back(a[i]+b[i]);
    }
    return c;
}

vector<int> sub(vector<int>&a,vector<int>&b){
    vector<int> c;
    rep(i,a.size()){
        c.push_back(a[i]-b[i]);
    }
    return c;
}
vector<int> zero(52);
class MinCostFlow{
public:
    struct edge{
        int to;
        int cap;
        vector<int> cost;
        int rev;
    };
    const int V;
    vector<int> inf;
    vector<vector<edge> > G;
    vector<vector<int> > h,dist;
    vector<int> prevv,preve;
    MinCostFlow(int node_size) : V(node_size),G(V),h(V,vector<int>(52,0)),dist(V),prevv(V),preve(V){
        inf = vector<int>(52,-1000000);
    }
    void add_edge(int from,int to,int cap,vector<int> cost){
        G[from].push_back((edge){to,cap,cost,(int)G[to].size()});
        G[to].push_back((edge){from,0,neg(cost),(int)G[from].size()-1});
    }
    vector<int> solve(int s,int t,int f){
        vector<int> res(52);
        while(f>0){
            // cerr << f << endl;
            auto cmp =[](pair<vector<int>,int>a,pair<vector<int>,int>b){ // a< b
                for(int i=0;i<=51;i++){
                    if(a.first[i]<b.first[i])return false;
                    if(a.first[i]>b.first[i])return true;
                }
                return a.second < b.second;
            };
            priority_queue<pair<vector<int>,int>,vector<pair<vector<int>,int>>,decltype(cmp)>que(cmp);
            fill(dist.begin(),dist.end(),inf);
            fill(dist[s].begin(),dist[s].end(),0);
            que.push(MP(zero,s));
            while(!que.empty()){
                auto p = que.top();
                que.pop();
                int v = p.second;
                // cerr << v << endl;
                // rep(i,52){
                //     cerr << dist[v][i] << " ";
                // }
                // cerr << endl;
                // cerr << p.second << endl;
                // rep(i,52){
                //     cerr << p.first[i] << " ";
                // }
                // cerr << endl;
                if(comp(dist[v],p.first))continue;
                // cerr << "test" << endl;
                for(int i=0;i<(int)G[v].size();i++){
                    edge& e = G[v][i];
                    auto X = add(dist[v],e.cost);
                    X = add(X,h[v]);
                    X = sub(X,h[e.to]);
                    if(e.cap > 0 && comp(X,dist[e.to])){
                        dist[e.to] = X;
                        prevv[e.to] = v,preve[e.to] = i;
                        que.push(MP(dist[e.to],e.to));
                    }
                }
            }
            // if(dist[t]==inf)return vector<int>();
            for(int i=0;i<V;i++){
                h[i] = add(h[i],dist[i]);
            }
            int d = f;
            for(int v=t;v!=s;v=prevv[v]){
                d = min(d,G[prevv[v]][preve[v]].cap);
            }
            // cerr << "d:" << d << endl;
            // rep(i,52){
            //     cerr << h[t][i] << " ";
            // }
            // cerr << endl;
            f -= d;
            res = add(res,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;
    }
};


int main(){
    int n;
    cin >> n;
    map<char,vector<int>> mp;
    rep(i,26){
        vector<int> a(52);
        a[i]++;
        mp['A'+i] =a; 
    }
    rep(i,26){
        vector<int> a(52);
        a[i+26]++;
        mp['a'+i] =a; 
    }
    MinCostFlow mf(2*n+2);
    int S = 2*n;
    int T = 2*n+1;
    vector<int> p(52);
    rep(i,n){
        mf.add_edge(S,i,1,p);
        mf.add_edge(i+n,T,1,p);
    }
    rep(i,n){
        string s;
        cin >> s;
        rep(j,n){
            mf.add_edge(i,j+n,1,mp[s[j]]);
        }    
    }
    auto res = mf.solve(S,T,n);
    rep(i,52){
        if(res[i]!=0){
            if(i<26){
                rep(z,res[i])cout << (char)('A'+i);
            }else{
                rep(z,res[i])cout << (char)('a'+i-26);
            }
        }
    }
    cout << endl;
    return 0;
}
accuracy: 1
time_ms: 1060
memory_kb: 4480
final_rank: 14

code_snippet: #include<bits/stdc++.h>
using namespace std;
#define rep(i,n) for(int i=0;i<(int)(n);i++)
#define MP make_pair
typedef long long ll;

//std::priority_queue<T,std::vector<T>,decltype(comp)> Q(comp)


bool comp(vector<int>&a,vector<int>&b){ // a< b
    for(int i=0;i<=51;i++){
        if(a[i]<b[i])return 0;
        if(a[i]>b[i])return 1;
    }
    return 0;
}

vector<int> neg(vector<int>a){
    vector<int> p;
    for(auto x:a){
        p.push_back(-x);
    }
    return p;
}
vector<int> add(vector<int>&a,vector<int>&b){
    vector<int> c;
    rep(i,a.size()){
        c.push_back(a[i]+b[i]);
    }
    return c;
}

vector<int> sub(vector<int>&a,vector<int>&b){
    vector<int> c;
    rep(i,a.size()){
        c.push_back(a[i]-b[i]);
    }
    return c;
}
vector<int> zero(52);
class MinCostFlow{
public:
    struct edge{
        int to;
        int cap;
        vector<int> cost;
        int rev;
    };
    const int V;
    vector<int> inf;
    vector<vector<edge> > G;
    vector<vector<int> > h,dist;
    vector<int> prevv,preve;
    MinCostFlow(int node_size) : V(node_size),G(V),h(V,vector<int>(52,0)),dist(V),prevv(V),preve(V){
        inf = vector<int>(52,-1000000);
    }
    void add_edge(int from,int to,int cap,vector<int> cost){
        G[from].push_back((edge){to,cap,cost,(int)G[to].size()});
        G[to].push_back((edge){from,0,neg(cost),(int)G[from].size()-1});
    }
    vector<int> solve(int s,int t,int f){
        vector<int> res(52);
        while(f>0){
            // cerr << f << endl;
            auto cmp =[](pair<vector<int>,int>a,pair<vector<int>,int>b){ // a< b
                for(int i=0;i<=51;i++){
                    if(a.first[i]<b.first[i])return false;
                    if(a.first[i]>b.first[i])return true;
                }
                return a.second < b.second;
            };
            priority_queue<pair<vector<int>,int>,vector<pair<vector<int>,int>>,decltype(cmp)>que(cmp);
            fill(dist.begin(),dist.end(),inf);
            fill(dist[s].begin(),dist[s].end(),0);
            que.push(MP(zero,s));
            while(!que.empty()){
                auto p = que.top();
                que.pop();
                int v = p.second;
                // cerr << v << endl;
                // rep(i,52){
                //     cerr << dist[v][i] << " ";
                // }
                // cerr << endl;
                // cerr << p.second << endl;
                // rep(i,52){
                //     cerr << p.first[i] << " ";
                // }
                // cerr << endl;
                if(comp(dist[v],p.first))continue;
                // cerr << "test" << endl;
                for(int i=0;i<(int)G[v].size();i++){
                    edge& e = G[v][i];
                    auto X = add(dist[v],e.cost);
                    X = add(X,h[v]);
                    X = sub(X,h[e.to]);
                    if(e.cap > 0 && comp(X,dist[e.to])){
                        dist[e.to] = X;
                        prevv[e.to] = v,preve[e.to] = i;
                        que.push(MP(dist[e.to],e.to));
                    }
                }
            }
            // if(dist[t]==inf)return vector<int>();
            for(int i=0;i<V;i++){
                h[i] = add(h[i],dist[i]);
            }
            int d = f;
            for(int v=t;v!=s;v=prevv[v]){
                d = min(d,G[prevv[v]][preve[v]].cap);
            }
            // cerr << "d:" << d << endl;
            // rep(i,52){
            //     cerr << h[t][i] << " ";
            // }
            // cerr << endl;
            f -= d;
            res = add(res,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;
    }
};


int main(){
    int n;
    cin >> n;
    map<char,vector<int>> mp;
    rep(i,26){
        vector<int> a(52);
        a[i]++;
        mp['A'+i] =a; 
    }
    rep(i,26){
        vector<int> a(52);
        a[i+26]++;
        mp['a'+i] =a; 
    }
    MinCostFlow mf(2*n+2);
    int S = 2*n;
    int T = 2*n+1;
    vector<int> p(52);
    rep(i,n){
        mf.add_edge(S,i,1,p);
        mf.add_edge(i+n,T,1,p);
    }
    rep(i,n){
        string s;
        cin >> s;
        rep(j,n){
            mf.add_edge(i,j+n,1,mp[s[j]]);
        }    
    }
    auto res = mf.solve(S,T,n);
    rep(i,52){
        if(res[i]!=0){
            if(i<26){
                rep(z,res[i])cout << (char)('A'+i);
            }else{
                rep(z,res[i])cout << (char)('a'+i-26);
            }
        }
    }
    cout << endl;
    return 0;
}
accuracy: 1
time_ms: 990
memory_kb: 4528
final_rank: 13

code_snippet: #include<bits/stdc++.h>
using namespace std;
#define rep(i,n) for(int i=0;i<(int)(n);i++)
#define MP make_pair
typedef long long ll;

//std::priority_queue<T,std::vector<T>,decltype(comp)> Q(comp)


bool comp(vector<int>&a,vector<int>&b){ // a< b
    for(int i=51;i>=0;i--){
        if(a[i]<b[i])return 1;
        if(a[i]>b[i])return 0;
    }
    return 0;
}

vector<int> neg(vector<int>a){
    vector<int> p;
    for(auto x:a){
        p.push_back(-x);
    }
    return p;
}
vector<int> add(vector<int>&a,vector<int>&b){
    vector<int> c;
    rep(i,a.size()){
        c.push_back(a[i]+b[i]);
    }
    return c;
}

vector<int> sub(vector<int>&a,vector<int>&b){
    vector<int> c;
    rep(i,a.size()){
        c.push_back(a[i]-b[i]);
    }
    return c;
}
vector<int> zero(52);
class MinCostFlow{
public:
    struct edge{
        int to;
        int cap;
        vector<int> cost;
        int rev;
    };
    const int V;
    vector<int> inf;
    vector<vector<edge> > G;
    vector<vector<int> > h,dist;
    vector<int> prevv,preve;
    MinCostFlow(int node_size) : V(node_size),G(V),h(V,vector<int>(52,0)),dist(V),prevv(V),preve(V){
        inf = vector<int>(52,10000);
    }
    void add_edge(int from,int to,int cap,vector<int> cost){
        G[from].push_back((edge){to,cap,cost,(int)G[to].size()});
        G[to].push_back((edge){from,0,neg(cost),(int)G[from].size()-1});
    }
    vector<int> solve(int s,int t,int f){
        vector<int> res(52);
        while(f>0){
            // cerr << f << endl;
            auto cmp =[](pair<vector<int>,int>a,pair<vector<int>,int>b){ // a< b
                for(int i=51;i>=0;i--){
                    if(a.first[i]<b.first[i])return true;
                    if(a.first[i]>b.first[i])return false;
                }
                return a.second < b.second;
            };
            priority_queue<pair<vector<int>,int>,vector<pair<vector<int>,int>>,decltype(cmp)>que(cmp);
            fill(dist.begin(),dist.end(),inf);
            fill(dist[s].begin(),dist[s].end(),0);
            que.push(MP(zero,s));
            while(!que.empty()){
                auto p = que.top();
                que.pop();
                int v = p.second;
                // cerr << v << endl;
                // rep(i,52){
                //     cerr << dist[v][i] << " ";
                // }
                // cerr << endl;
                // cerr << p.second << endl;
                // rep(i,52){
                //     cerr << p.first[i] << " ";
                // }
                // cerr << endl;
                if(comp(dist[v],p.first))continue;
                // cerr << "test" << endl;
                for(int i=0;i<(int)G[v].size();i++){
                    edge& e = G[v][i];
                    auto X = add(dist[v],e.cost);
                    X = add(X,h[v]);
                    X = sub(X,h[e.to]);
                    if(e.cap > 0 && comp(X,dist[e.to])){
                        dist[e.to] = X;
                        prevv[e.to] = v,preve[e.to] = i;
                        que.push(MP(dist[e.to],e.to));
                    }
                }
            }
            // if(dist[t]==inf)return vector<int>();
            for(int i=0;i<V;i++){
                h[i] = add(h[i],dist[i]);
            }
            int d = f;
            for(int v=t;v!=s;v=prevv[v]){
                d = min(d,G[prevv[v]][preve[v]].cap);
            }
            // cerr << "d:" << d << endl;
            // rep(i,52){
            //     cerr << h[t][i] << " ";
            // }
            // cerr << endl;
            f -= d;
            res = add(res,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;
    }
};


int main(){
    int n;
    cin >> n;
    map<char,vector<int>> mp;
    rep(i,26){
        vector<int> a(52);
        a[i]++;
        mp['A'+i] =a; 
    }
    rep(i,26){
        vector<int> a(52);
        a[i+26]++;
        mp['a'+i] =a; 
    }
    MinCostFlow mf(2*n+2);
    int S = 2*n;
    int T = 2*n+1;
    vector<int> p(52);
    rep(i,n){
        mf.add_edge(S,i,1,p);
        mf.add_edge(i+n,T,1,p);
    }
    rep(i,n){
        string s;
        cin >> s;
        rep(j,n){
            mf.add_edge(i,j+n,1,mp[s[j]]);
        }    
    }
    auto res = mf.solve(S,T,n);
    rep(i,52){
        if(res[i]!=0){
            if(i<26){
                rep(z,res[i])cout << (char)('A'+i);
            }else{
                rep(z,res[i])cout << (char)('a'+i-26);
            }
        }
    }
    cout << endl;
    return 0;
}
accuracy: 0.06976744186046512
time_ms: 120
memory_kb: 3988
final_rank: 18

code_snippet: #include <bits/stdc++.h>
using namespace std;
typedef long long ll;
template<class T>bool chmax(T &a, const T &b) { if (a<b) { a=b; return true; } return false; }
template<class T>bool chmin(T &a, const T &b) { if (b<a) { a=b; return true; } return false; }
#define all(x) (x).begin(),(x).end()
#define fi first
#define se second
//#define mp make_pair
#define si(x) int(x.size())
const int mod=998244353,MAX=105;
//const int INF=1<<28;
//const ll INF=1LL<<60;
#include <boost/multiprecision/cpp_int.hpp>
namespace mp = boost::multiprecision;
const int D=120;

typedef pair<mp::cpp_int,int> P;

struct edge{
    int to;
    int cap;
    mp::cpp_int cost;
    int rev;
};

int V;
vector<edge> G[MAX];
mp::cpp_int h[MAX];
mp::cpp_int dist[MAX];
int prevv[MAX],preve[MAX];

mp::cpp_int rui[200];

mp::cpp_int INF;

void add_edge(int from,int to,int cap,mp::cpp_int cost){
    G[from].push_back((edge){to,cap,cost,int(G[to].size())});
    G[to].push_back((edge){from,0,-cost,int(G[from].size()-1)});
}

mp::cpp_int min_cost_flow(int s,int t,int f){
    mp::cpp_int 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;i<V;i++) dist[i]=INF;
        dist[s]=0;
        que.push(P(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<si(G[v]);i++){
                edge &e=G[v][i];
                mp::cpp_int cost=dist[v]+e.cost+h[v]-h[e.to];
                if(e.cap>0&&dist[e.to]>cost){
                    dist[e.to]=cost;
                    prevv[e.to]=v;
                    preve[e.to]=i;
                    que.push(P(dist[e.to],e.to));
                }
            }
        }
        
        if(dist[t]==INF) return INF;
        for(int v=0;v<V;v++){
            h[v]+=dist[v];
        }
        
        int d=f;
        for(int v=t;v!=s;v=prevv[v]){
            d=min(d,G[prevv[v]][preve[v]].cap);
        }
        f-=d;
        
        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;
        }
    }
    return res;
}//sからtへの流量fの最小費用流、流せない場合は-1

int main(){
    
    std::ifstream in("text.txt");
    std::cin.rdbuf(in.rdbuf());
    cin.tie(0);
    ios::sync_with_stdio(false);
    
    int N;cin>>N;
    vector<string> S(N);
    for(int i=0;i<N;i++) cin>>S[i];
    
    rui[0]=1;
    for(int i=1;i<170;i++) rui[i]=rui[i-1]*10;
    
    INF=rui[150];
    
    V=2*N+2;
    int s=2*N,t=2*N+1;
    for(int i=0;i<N;i++){
        for(int j=0;j<N;j++){
            mp::cpp_int cost;
            if('A'<=S[i][j]&&S[i][j]<='Z') cost=rui[120]-rui[102-2*(S[i][j]-'A')];
            else cost=rui[120]-rui[50-2*(S[i][j]-'a')];
            add_edge(i,N+j,1,cost);
        }
        add_edge(s,i,1,0);
        add_edge(N+i,t,1,0);
    }
    
    mp::cpp_int res=min_cost_flow(s,t,N);
    
    res-=rui[120]*N;
    res*=-1;
    
    string SS;
    for(char c='A';c<='Z';c++) SS+=c;
    for(char c='a';c<='z';c++) SS+=c;
    
    reverse(all(SS));
    
    string T;
    for(int i=0;i<52;i++){
        mp::cpp_int k=res%100;
        res/=100;
        while(k--) T+=SS[i];
    }
    
    reverse(all(T));
    
    cout<<T<<endl;
}
accuracy: 1
time_ms: 20
memory_kb: 3964
final_rank: 3

code_snippet: #include <bits/stdc++.h>
using namespace std;
template<class T> inline bool chmax(T& a, T b) { if (a < b) { a = b; return 1; } return 0; }
template<class T> inline bool chmin(T& a, T b) { if (a > b) { a = b; return 1; } return 0; }

#define COUT(x) cout << #x << " = " << (x) << " (L" << __LINE__ << ")" << endl
template<class T1, class T2> ostream& operator << (ostream &s, pair<T1,T2> P)
{ return s << '<' << P.first << ", " << P.second << '>'; }
template<class T> ostream& operator << (ostream &s, vector<T> P)
{ for (int i = 0; i < P.size(); ++i) { if (i > 0) { s << " "; } s << P[i]; } return s; }
template<class T> ostream& operator << (ostream &s, vector<vector<T> > P)
{ for (int i = 0; i < P.size(); ++i) { s << endl << P[i]; } return s << endl; }
template<class T> ostream& operator << (ostream &s, set<T> P)
{ for(auto it : P) { s << "<" << it << "> "; } return s << endl; }
template<class T1, class T2> ostream& operator << (ostream &s, map<T1,T2> P)
{ for(auto it : P) { s << "<" << it.first << "->" << it.second << "> "; } return s << endl; }

const int DEFAULT_SIZE = 125;
struct bint : vector<long long> {
    static const long long BASE = 100000000;
    static const int BASE_DIGIT = 8;
    int sign;

    // constructor
    bint(long long num = 0) : vector<long long>(DEFAULT_SIZE, 0), sign(1) {
        if (num < 0) sign = -1, num = -num;
        (*this)[0] = num; 
        this->normalize();
    }
    bint(int size, long long num) : vector<long long>(size, num), sign(1) {}
    bint& normalize() {
        long long c = 0;
        bool exist = false;
        for (int i = 0;; ++i) {
            if (i >= this->size()) this->push_back(0);
            if ((*this)[i] < 0 && i+1 >= this->size()) this->push_back(0);
            while ((*this)[i] < 0) {
                (*this)[i+1] -= 1;
                (*this)[i] += BASE;
            }
            long long a = (*this)[i] + c;
            (*this)[i] = a % BASE;
            if ((*this)[i]) exist = true;
            c = a / BASE;
            if (c == 0 && i == this->size()-1) break;
        }
        if (!exist) sign = 1;
        return (*this);
    }
    friend bint abs(const bint &x) {
        bint z = x;
        if (z.sign == -1) z.sign = 1;
        return z;
    }

    // operation
    bint operator - () const {
        bint res = *this;
        bool allzero = true;
        for (int i = 0; i < this->size(); ++i) {
            if (res[i] != 0) {
                allzero = false;
                break;
            }
        }
        if (!allzero) res.sign = -res.sign; 
        return res; 
    }
    bint& operator += (const bint& r) {
        while (size() < r.size()) this->emplace_back(0);
        if (sign == r.sign) {
            for (int i = 0; i < r.size(); ++i) (*this)[i] += r[i];
        }
        else {
            if (sign == 1 && abs(*this) < abs(r)) sign = -1;
            else if (sign == -1 && abs(*this) <= abs(r)) sign = 1;
            if (abs(*this) >= abs(r)) {
                for (int i = 0; i < r.size(); ++i) (*this)[i] -= r[i];
            }
            else {
                for (int i = 0; i < size(); ++i) (*this)[i] = -(*this)[i];
                for (int i = 0; i < r.size(); ++i) (*this)[i] += r[i];
            }
        }
        return this->normalize();
    }
    bint& operator -= (const bint& r) {
        while (size() < r.size()) this->emplace_back(0);
        if (sign == -r.sign) {
            for (int i = 0; i < r.size(); ++i) (*this)[i] += r[i];
        }
        else {
            if (sign == 1 && abs(*this) < abs(r)) sign = -1;
            else if (sign == -1 && abs(*this) <= abs(r)) sign = 1;
            if (abs(*this) >= abs(r)) {
                for (int i = 0; i < r.size(); ++i) (*this)[i] -= r[i];
            }
            else {
                for (int i = 0; i < size(); ++i) (*this)[i] = -(*this)[i];
                for (int i = 0; i < r.size(); ++i) (*this)[i] += r[i];
            }
        }
        return this->normalize();
    }
    bint& operator *= (long long r) {
        if ( (sign == 1 && r >= 0) || (sign == -1 && r < 0) ) sign = 1;
        else sign = -1;
        if (r < 0) r = -r;
        for (int i = 0; i < size(); ++i) (*this)[i] *= r;
        return this->normalize();
    }
    bint& operator *= (const bint& r) {
        int tx = (int)size()-1, ty = (int)r.size()-1; 
        for (tx = size()-1; tx >= 0; --tx) if ((*this)[tx] > 0) break;
        for (ty = r.size()-1; ty >= 0; --ty) if (r[ty] > 0) break;
        bint res(0);
        res.resize(tx+ty+2);
        if (sign == r.sign) res.sign = 1;
        else res.sign = -1;
        for (int i = 0; i <= tx; ++i) {
            for (int j = 0; j <= ty && i+j < (int)res.size()-1; ++j) {
                long long val = (*this)[i] * r[j] + res[i+j];
                res[i+j+1] += val / bint::BASE;
                res[i+j] = val % bint::BASE;
            }
        }
        return (*this) = res.normalize();
    }
    friend bint pow(const bint& a, long long n) {
        bint res(1), b = a;
        while (n > 0) {
            if (n & 1) res = res * b;
            b = b * b;
            n >>= 1;
        }
        return res;
    }
    bint operator + (const bint& r) const { return bint(*this) += r; }
    bint operator - (const bint& r) const { return bint(*this) -= r; }
    bint operator * (long long r) const { return bint(*this) *= r; }
    bint operator * (const bint& r) const { return bint(*this) *= r; }

    // divide
    bint& operator /= (long long r) {
        if (r < 0) sign *= -1, r = -r;
        long long c = 0, t = 0;
        for (int i = (int)size()-1; i >= 0; --i) {
            t = bint::BASE * c + (*this)[i];
            (*this)[i] = t / r;
            c = t % r;
        }
        this->normalize();
        return (*this);
    }
    long long operator %= (long long r) {
        if (r < 0) sign *= -1, r = -r;
        long long c = 0, t = 0;
        for (int i = (int)size()-1; i >= 0; --i) {
            t = bint::BASE * c + (*this)[i];
            (*this)[i] = t / r;
            c = t % r;
        }
        return c;
    }
    bint operator / (long long r) const {
        return bint(*this) /= r;
    }
    long long operator % (long long r) const {
        return bint(*this) %= r;
    }
    friend pair<bint, bint> divmod(const bint &a, const bint &r) {
        bint zero = 0, s = 0, t = 0;
        if (abs(a) < abs(r)) return {zero, a};
        bint ar = abs(r);
        s.resize((int)a.size()), t.resize((int)r.size());
        int tx = (int)a.size()-1;
        for (;tx >= 0; --tx) if (a[tx] > 0) break;
        for (int i = tx; i >= 0; --i) {
            t = t * bint::BASE + a[i];
            long long lo = 0, hi = bint::BASE;
            if (t >= ar) {
                while (hi - lo > 1) {
                    int mid = (hi + lo) / 2;
                    if (ar * mid > t) hi = mid;
                    else lo = mid;
                }
                t -= ar * lo;
            }
            s[i] = lo;
        }
        if (a.sign == r.sign) s.sign = 1, t.sign = 1;
        else s.sign = -1, t.sign = 1;
        return make_pair(s.normalize(), t.normalize());
    }
    bint operator / (const bint& r) const {
        return divmod((*this), r).first;
    }
    bint operator % (const bint& r) const {
        return divmod((*this), r).second;
    }
    bint& operator /= (const bint& r) { return (*this) = (*this) / r; }
    bint& operator %= (const bint& r) { return (*this) = (*this) % r; }

    // equality
    friend bool operator < (const bint &x, const bint& y) {
        if (x.sign < y.sign) return true;
        else if (x.sign > y.sign) return false;
        else {
            int tx = (int)x.size()-1, ty = (int)y.size()-1; 
            for (tx = x.size()-1; tx >= 0; --tx) if (x[tx] > 0) break;
            for (ty = y.size()-1; ty >= 0; --ty) if (y[ty] > 0) break;
            if (tx < ty) return true;
            else if (tx > ty) return false;
            else if (x.sign == 1) {
                for (int i = tx; i >= 0; --i)
                    if (x[i] != y[i]) return x[i] < y[i];
                return false;
            }
            else {
                for (int i = tx; i >= 0; --i)
                    if (x[i] != y[i]) return x[i] > y[i];
                return false;
            }
        }
    }
    friend bool operator > (const bint& x, const bint& y) { return y < x; }
    friend bool operator <= (const bint& x, const bint& y) { return !(x > y); }
    friend bool operator >= (const bint& x, const bint& y) { return !(x < y); }
    friend bool operator == (const bint &x, const bint& y) {
        if (x.sign != y.sign) return false;
        int tx = (int)x.size()-1, ty = (int)y.size()-1; 
        for (tx = x.size()-1; tx >= 0; --tx) if (x[tx] > 0) break;
        for (ty = y.size()-1; ty >= 0; --ty) if (y[ty] > 0) break;
        if (tx != ty) return false;
        for (int i = tx; i >= 0; --i)
            if (x[i] != y[i]) return false;
        return true;
    }
    friend bool operator != (const bint& x, const bint& y) { return !(x == y); }
};

bint toBint(const string &is) {
    string s = is;
    if (s[0] == '-') s = s.substr(1);
    while (s.size() % bint::BASE_DIGIT != 0) s = "0" + s;
    int N = (int)s.size();
    bint res(N/bint::BASE_DIGIT, 0);
    for (int i = 0; i < (int)s.size(); ++i) {
        res[(N-i-1)/bint::BASE_DIGIT] *= 10;
        res[(N-i-1)/bint::BASE_DIGIT] += s[i] - '0';
    }
    if (is[0] == '-') res.sign = -1;
    return res;
}

string toStr(const bint &r) {
    stringstream ss;
    if (r.sign == -1) ss << '-';
    int d = (int)r.size()-1; 
    for (; d >= 0; --d) if (r[d] > 0) break;
    if (d == -1) ss << 0;
    else ss << r[d];
    for (int i = d-1; i >= 0; --i) {
        ss.width(bint::BASE_DIGIT);
        ss.fill('0');
        ss << r[i];
    }
    return ss.str();
}

istream &operator >> (istream &is, bint &x) {
    string s; is >> s;
    x = toBint(s);
    return is;
}

ostream &operator << (ostream &os, const bint &x) {
    if (x.sign == -1) os << '-';
    int d = x.size()-1; 
    for (d = x.size()-1; d >= 0; --d) if (x[d] > 0) break;
    if (d == -1) os << 0;
    else os << x[d];
    for (int i = d-1; i >= 0; --i) {
        os.width(bint::BASE_DIGIT);
        os.fill('0');
        os << x[i];
    }
    return os;
}

// edge class (for network-flow)
template<class FLOWTYPE, class COSTTYPE> struct Edge {
    int rev, from, to, id;
    FLOWTYPE cap, icap;
    COSTTYPE cost;
    Edge(int r, int f, int t, FLOWTYPE ca, COSTTYPE co, int id = -1) :
        rev(r), from(f), to(t), cap(ca), icap(ca), cost(co), id(id) {}
    friend ostream& operator << (ostream& s, const Edge& E) {
        if (E.cap > 0)
            return s << E.from << "->" << E.to <<
                '(' << E.cap << ',' << E.cost << ')';
        else return s;
    }
};

// graph class (for network-flow)
template<class FLOWTYPE, class COSTTYPE> struct Graph {
    vector<vector<Edge<FLOWTYPE, COSTTYPE> > > list;
    
    Graph(int n = 0) : list(n) { }
    void init(int n = 0) { list.clear(); list.resize(n); }
    void reset() { for (int i = 0; i < (int)list.size(); ++i) for (int j = 0; j < list[i].size(); ++j) list[i][j].cap = list[i][j].icap; }
    inline vector<Edge<FLOWTYPE, COSTTYPE> >& operator [] (int i) { return list[i]; }
    inline const size_t size() const { return list.size(); }
    
    inline Edge<FLOWTYPE, COSTTYPE> &redge(const Edge<FLOWTYPE, COSTTYPE> &e) {
        if (e.from != e.to) return list[e.to][e.rev];
        else return list[e.to][e.rev + 1];
    }
    
    void addedge(int from, int to, FLOWTYPE cap, COSTTYPE cost, int id = -1) {
        list[from].push_back(Edge<FLOWTYPE, COSTTYPE>((int)list[to].size(), from, to, cap, cost, id));
        list[to].push_back(Edge<FLOWTYPE, COSTTYPE>((int)list[from].size() - 1, to, from, 0, -cost));
    }
    
    void add_undirected_edge(int from, int to, FLOWTYPE cap, COSTTYPE cost, int id = -1) {
        list[from].push_back(Edge<FLOWTYPE, COSTTYPE>((int)list[to].size(), from, to, cap, cost, id));
        list[to].push_back(Edge<FLOWTYPE, COSTTYPE>((int)list[from].size() - 1, to, from, cap, cost, id));
    }

    friend ostream& operator << (ostream& s, const Graph& G) {
        s << endl;
        for (int i = 0; i < G.size(); ++i) {
            s << i << ":";
            for (auto e : G.list[i]) s << " " << e;
            s << endl;
        }
        return s;
    }   
};

// min-cost flow (by primal-dual)
template<class FLOWTYPE, class COSTTYPE> COSTTYPE MinCostFlow(Graph<FLOWTYPE, COSTTYPE> &G, int s, int t, FLOWTYPE f) {
    int n = (int)G.size();
    vector<COSTTYPE> pot(n, 0), dist(n, -1);
    vector<int> prevv(n), preve(n);
    COSTTYPE res = 0;
    while (f > 0) {
        priority_queue<pair<COSTTYPE,int>, vector<pair<COSTTYPE,int> >, greater<pair<COSTTYPE,int> > > que;
        dist.assign(n, -1);
        dist[s] = 0;
        que.push(make_pair(0,s));
        while(!que.empty()) {
            pair<COSTTYPE,int> p = que.top();
            que.pop();
            int v = p.second;
            if (dist[v] < p.first) continue;
            for (int i = 0; i < G[v].size(); ++i) {
                auto e = G[v][i];
                if (e.cap > 0 && (dist[e.to] < 0 || dist[e.to] > dist[v] + e.cost + pot[v] - pot[e.to])) {
                    dist[e.to] = dist[v] + e.cost + pot[v] - pot[e.to];
                    prevv[e.to] = v;
                    preve[e.to] = i;
                    que.push(make_pair(dist[e.to], e.to));
                }
            }
        }
        if (dist[t] < 0) return -1;
        for (int v = 0; v < n; ++v) pot[v] += dist[v];
        FLOWTYPE d = f;
        for (int v = t; v != s; v = prevv[v]) {
            d = min(d, G[prevv[v]][preve[v]].cap);
        }
        f -= d;
        res += pot[t] * d;
        for (int v = t; v != s; v = prevv[v]) {
            Edge<FLOWTYPE,COSTTYPE> &e = G[prevv[v]][preve[v]];
            Edge<FLOWTYPE,COSTTYPE> &re = G.redge(e);
            e.cap -= d;
            re.cap += d;
        }
    }
    return res;
}

int main() {
    long long x = 55;
    vector<bint> power(300, 1);
    for (int i = 1; i < 300; ++i) power[i] = power[i-1] * x;
    bint M = power.back();

    int N;
    cin >> N;
    vector<string> fi(N);
    for (int i = 0; i < N; ++i) cin >>fi[i];
    int s = N*2, t = N*2+1;
    Graph<int,bint> G(N*2+2);
    auto cost = [&](char c) {
        if (c >= 'A' && c <= 'Z') return M - power[51-(c-'A')];
        else return M - power[25-(c-'a')];
    };
    for (int i = 0; i < N; ++i) {
        G.addedge(s, i, 1, 0), G.addedge(i+N, t, 1, 0);
        for (int j = 0; j < N; ++j) G.addedge(i, j+N, 1, cost(fi[i][j]));
    }
    auto mincost = MinCostFlow(G, s, t, N);

    // reconstruct
    mincost = M * N - mincost;
    vector<long long> hist;
    while (mincost > 0) {
        hist.push_back(mincost % x);
        mincost /= x;
    }
    string res = "";
    for (int i = 0; i < hist.size(); ++i) {
        char c;
        if (i < 26) c = 'z'-i;
        else c = 'Z'-(i-26);
        for (int j = 0; j < hist[i]; ++j) res += c;
    }
    reverse(res.begin(), res.end());
    cout << res << endl;
}
accuracy: 1
time_ms: 280
memory_kb: 9924
final_rank: 12

code_snippet: #define PROBLEM "http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=2679"

#include<bits/stdc++.h>
using namespace std;

#define call_from_test
#ifndef call_from_test
#include<bits/stdc++.h>
using namespace std;
#endif
//BEGIN CUT HERE
// O(F E log V)
template<typename Flow, typename Cost>
struct PrimalDual{
  struct Edge{
    int to;
    Flow cap;
    Cost cost;
    int rev;
    Edge(int to,Flow cap,Cost cost,int rev):
      to(to),cap(cap),cost(cost),rev(rev){}
  };

  vector<vector<Edge>> G;
  vector<Cost> h,dist;
  vector<int> prevv,preve;

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

  void add_edge(int u,int v,Flow cap,Cost cost){
    int e=G[u].size();
    int r=(u==v?e+1:G[v].size());
    G[u].emplace_back(v,cap,cost,r);
    G[v].emplace_back(u,0,-cost,e);
  }

  void dijkstra(int s){
    struct P{
      Cost first;
      int second;
      P(Cost first,int second):first(first),second(second){}
      bool operator<(const P&a) const{return first>a.first;}
    };
    priority_queue<P> pq;

    dist[s]=0;
    pq.emplace(dist[s],s);
    while(!pq.empty()){
      P p=pq.top();pq.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;
          pq.emplace(dist[e.to],e.to);
        }
      }
    }
  }

  Cost res;

  bool build(int s,int t,Flow f){
    res=0;
    fill(h.begin(),h.end(),0);
    const Cost INF = numeric_limits<Cost>::max();
    while(f>0){
      fill(dist.begin(),dist.end(),INF);
      dijkstra(s);
      if(dist[t]==INF) return false;

      for(int v=0;v<(int)h.size();v++)
        if(dist[v]<INF) h[v]=h[v]+dist[v];

      Flow 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;
      }
    }
    return true;
  }

  Cost get_cost(){return res;}
};
//END CUT HERE
#ifndef call_from_test
//INSERT ABOVE HERE
signed geocon2013_B(){
  cin.tie(0);
  ios::sync_with_stdio(0);

  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])));

  assert(G.build(S,T,f));
  cout<<fixed<<setprecision(12)<<G.get_cost()<<endl;
  return 0;
}
/*
  verified on 2020/09/24
  https://atcoder.jp/contests/geocon2013/tasks/geocon2013_b
*/

signed main(){
  geocon2013_B();
  return 0;
}
#endif

#undef call_from_test

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;
  }
  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());};
  };
}

int main(){
  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);
    }
  }

  assert(G.build(S,T,n));
  auto res=G.get_cost();

  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;
}
accuracy: 1
time_ms: 60
memory_kb: 4828
final_rank: 7

code_snippet: #include<iostream>
#include<array>
using namespace std;
void dump(const array<int,52>&A)
{
	for(int i=0;i<52;i++)
	{
		cout<<A[i];
		if(i==51)cout<<endl;
		else cout<<",";
	}
}
array<int,52>MAX,ZERO;
array<int,52>operator-(const array<int,52>&A)
{
	array<int,52>ret=A;
	for(int i=0;i<52;i++)ret[i]=-ret[i];
	return ret;
}
array<int,52>operator+(const array<int,52>&A,const array<int,52>&B)
{
	array<int,52>ret=ZERO;
	for(int i=0;i<52;i++)ret[i]=A[i]+B[i];
	return ret;
}
array<int,52>operator-(const array<int,52>&A,const array<int,52>&B)
{
	array<int,52>ret=ZERO;
	for(int i=0;i<52;i++)ret[i]=A[i]-B[i];
	return ret;
}
array<int,52>operator*(const array<int,52>&A,const int&x)
{
	array<int,52>ret=ZERO;
	for(int i=0;i<52;i++)ret[i]=A[i]*x;
	return ret;
}
bool operator<(const array<int,52>&A,const array<int,52>&B)
{
	for(int i=0;i<52;i++)if(A[i]!=B[i])return A[i]>B[i];
	return false;
}
//Minimum Cost Flow O(FE log V)
#include<algorithm>
#include<utility>
#include<vector>
#include<queue>
#include<limits>
template<typename T>
struct MCF{
	struct edge{
		int to,rev,cap;
		T cost;
	};
	vector<vector<edge> >G;
	vector<T>h,d;
	vector<int>pv,pe;
	MCF(int n_=0):G(n_),h(n_,ZERO),d(n_),pv(n_),pe(n_){}
	void add_edge(int from,int to,int cap,T cost)
	{
		G[from].push_back({to,(int)G[to].size(),cap,cost});
		G[to].push_back({from,(int)G[from].size()-1,0,-cost});
	}
	T min_cost_flow(int s,int t,int f)//ans or -1
	{
		T ret=ZERO;
		while(f>0)
		{
			priority_queue<pair<T,int>,vector<pair<T,int> >,greater<pair<T,int> > >P;
			fill(d.begin(),d.end(),MAX);
			d[s]=ZERO;
			P.push(make_pair(ZERO,s));
			while(!P.empty())
			{
				pair<T,int>p=P.top();P.pop();
				if(d[p.second]<p.first)continue;
				for(int i=0;i<G[p.second].size();i++)
				{
					edge&e=G[p.second][i];
					if(e.cap>0&&d[p.second]+e.cost+h[p.second]-h[e.to]<d[e.to])
					{
						d[e.to]=d[p.second]+e.cost+h[p.second]-h[e.to];
						pv[e.to]=p.second;
						pe[e.to]=i;
						P.push(make_pair(d[e.to],e.to));
					}
				}
			}
			if(d[t]==MAX)return MAX;
			for(int u=0;u<G.size();u++)h[u]=h[u]+d[u];
			int d=f;
			for(int u=t;u!=s;u=pv[u])d=min(d,G[pv[u]][pe[u]].cap);
			f-=d;
			ret=ret+h[t]*d;
			for(int u=t;u!=s;u=pv[u])
			{
				G[pv[u]][pe[u]].cap-=d;
				G[u][G[pv[u]][pe[u]].rev].cap+=d;
			}
		}
		return ret;
	}
};
main()
{
	for(int i=0;i<52;i++)MAX[i]=-114514;
	int N;
	cin>>N;
	vector<string>A(N);
	for(int i=0;i<N;i++)cin>>A[i];
	MCF<array<int,52> >P(2*N+2);
	for(int i=0;i<N;i++)
	{
		P.add_edge(2*N,i,1,ZERO);
		P.add_edge(N+i,2*N+1,1,ZERO);
	}
	for(int i=0;i<N;i++)for(int j=0;j<N;j++)
	{
		array<int,52>cost=ZERO;
		if(A[i][j]<='Z')
		{
			cost[A[i][j]-'A']=1;
		}
		else
		{
			cost[A[i][j]-'a'+26]=1;
		}
		P.add_edge(i,N+j,1,cost);
	}
	array<int,52>ans=P.min_cost_flow(2*N,2*N+1,N);
	for(int i=0;i<52;i++)for(int j=0;j<ans[i];j++)
	{
		char c=i<26?i+'A':i-26+'a';
		cout<<c;
	}
	cout<<endl;
}
accuracy: 1
time_ms: 70
memory_kb: 4500
final_rank: 8

code_snippet: #include <iostream>
#include <vector>
#include <algorithm>
#include <array>
using namespace std;
typedef array<int, 52> rexico;

rexico operator -(const rexico &a){
    auto res = a;
    for(auto &e: res){
        e = -e;
    }
    return res;
}
rexico& operator +=(rexico &a, const rexico &b){
    for(int i=0; i<52; i++){
        a[i] += b[i];
    }
    return a;
}
rexico operator +(const rexico &a, const rexico &b){
    auto res = a;
    return res += b;
}
rexico operator *(const int &c, const rexico &a){
    auto res = a;
    for(auto &e: res){
        e *= c;
    }
    return res;
}


template<typename T>
struct MincostFlow{
    struct edge{
        int to, rev;
        int cap;
        T cost;
        edge(int to, int rev, int cap, T cost)
            :to(to),rev(rev),cap(cap),cost(cost){}
        edge(){}
    };
    
    int n;
    T zero;
    T inf;
    vector<vector<edge>> graph;

    MincostFlow(int n, T zero, T inf):n(n),zero(zero),inf(inf){
        graph.resize(n);
    }
    void add_edge(int from, int to, int cap, T cost){
        graph[from].emplace_back(to, graph[to].size(), cap, cost);
        graph[to].emplace_back(from, (int)graph[from].size()-1, 0, -cost);
    }
    T exec(int s, int g, int f){
        T res = zero;
        while(f > 0){
            vector<int> prevv(n), preve(n);
            vector<T> mincost(n, inf);
            mincost[s] = zero;
            while(1){
                bool update = false;
                for(int i=0; i<n; i++){
                    if(mincost[i] == inf) continue;
                    for(int j=0; j<(int)graph[i].size(); j++){
                        edge &e = graph[i][j];
                        if(e.cap>0 && mincost[i] +e.cost < mincost[e.to]){
                            mincost[e.to] = mincost[i] +e.cost;
                            prevv[e.to] = i;
                            preve[e.to] = j;
                            update = true;
                        }
                    }
                }
                if(!update) break;
            }
            if(mincost[g] == inf){
                return inf;
            }
         
            int d = f;
            for(int v=g; v!=s; v=prevv[v]){
                d = min(d, graph[prevv[v]][preve[v]].cap);
            }
            f -= d;
            res += d*mincost[g];
            for(int v=g; v!=s; v=prevv[v]){
                edge &e = graph[prevv[v]][preve[v]];
                e.cap -= d;
                graph[v][e.rev].cap += d;
            }
        }
        return res;
    }
};

char int2char(int n){
    if(n<26) return 'A'+n;
    return 'a'+n-26;
}
int char2int(char c){
    if('A'<=c and c<='Z'){
        return c-'A';
    }
    return c-'a'+26;
}
rexico conv(char c){
    rexico res{};
    res[char2int(c)] = -1;
    return res;
}

int main(){
    rexico zero{}, inf;
    for(int i=0; i<52; i++){
        inf[i] = 1e9;
    }
    int n;
    cin >> n;
    vector<string> s(n);
    for(int i=0; i<n; i++){
        cin >> s[i];
    }
    MincostFlow<rexico> mcf(2*n+2, zero, inf);
    for(int i=0; i<n; i++){
        for(int j=0; j<n; j++){
            mcf.add_edge(i, n+j, 1, conv(s[i][j]));
        }
        mcf.add_edge(2*n, i, 1, zero);
        mcf.add_edge(n+i, 2*n+1, 1, zero);
    }
    rexico ret = mcf.exec(2*n, 2*n+1, n);
    string ans = "";
    for(int i=0; i<52; i++){
        ans += string(-ret[i], int2char(i));
    }
    sort(ans.begin(), ans.end());
    cout << ans << endl;
    return 0;
}
accuracy: 1
time_ms: 30
memory_kb: 4456
final_rank: 4

code_snippet: #include<iostream>
#include<string>
#include<cstdio>
#include<vector>
#include<cmath>
#include<algorithm>
#include<functional>
#include<iomanip>
#include<queue>
#include<ciso646>
#include<random>
#include<map>
#include<set>
#include<bitset>
#include<stack>
#include<unordered_map>
#include<utility>
#include<cassert>
#include<complex>
using namespace std;

//#define int long long
typedef long long ll;

typedef unsigned long long ul;
typedef unsigned int ui;
const ll mod = 1000000007;
const ll INF = (1e+18) + 7;
typedef pair<int, int>P;
#define stop char nyaa;cin>>nyaa;
#define rep(i,n) for(int i=0;i<n;i++)
#define per(i,n) for(int i=n-1;i>=0;i--)
#define Rep(i,sta,n) for(int i=sta;i<n;i++)
#define rep1(i,n) for(int i=1;i<=n;i++)
#define per1(i,n) for(int i=n;i>=1;i--)
#define Rep1(i,sta,n) for(int i=sta;i<=n;i++)
#define all(v) (v).begin(),(v).end()
typedef pair<ll, ll> LP;
typedef long double ld;
typedef pair<ld, ld> LDP;
const ld eps = 1e-6;
const ld pi = acos(-1.0);
//typedef vector<vector<ll>> mat;
typedef vector<int> vec;

ll mod_pow(ll a, ll n) {
	ll res = 1;
	while (n) {
		if (n & 1)res = res * a%mod;
		a = a * a%mod; n >>= 1;
	}
	return res;
}

struct modint {
	ll n;
	modint() :n(0) { ; }
	modint(ll m) :n(m) {
		if (n >= mod)n %= mod;
		else if (n < 0)n = (n%mod + mod) % mod;
	}
	operator int() { return n; }
};
bool operator==(modint a, modint b) { return a.n == b.n; }
modint operator+=(modint &a, modint b) { a.n += b.n; if (a.n >= mod)a.n -= mod; return a; }
modint operator-=(modint &a, modint b) { a.n -= b.n; if (a.n < 0)a.n += mod; return a; }
modint operator*=(modint &a, modint b) { a.n = ((ll)a.n*b.n) % mod; return a; }
modint operator+(modint a, modint b) { return a += b; }
modint operator-(modint a, modint b) { return a -= b; }
modint operator*(modint a, modint b) { return a *= b; }
modint operator^(modint a, int n) {
	if (n == 0)return modint(1);
	modint res = (a*a) ^ (n / 2);
	if (n % 2)res = res * a;
	return res;
}

ll inv(ll a, ll p) {
	return (a == 1 ? 1 : (1 - p * inv(p%a, a)) / a + p);
}
modint operator/(modint a, modint b) { return a * modint(inv(b, mod)); }

//const int max_n = 1 << 22;
//modint fact[max_n], factinv[max_n];
//void init_f() {
//	fact[0] = modint(1);
//	for (int i = 0; i < max_n - 1; i++) {
//		fact[i + 1] = fact[i] * modint(i + 1);
//	}
//	factinv[max_n - 1] = modint(1) / fact[max_n - 1];
//	for (int i = max_n - 2; i >= 0; i--) {
//		factinv[i] = factinv[i + 1] * modint(i + 1);
//	}
//}
//modint comb(int a, int b) {
//	if (a < 0 || b < 0 || a < b)return 0;
//	return fact[a] * factinv[b] * factinv[a - b];
//}


int dx[4] = { 0,1,0,-1 };
int dy[4] = { 1,0,-1,0 };

using poly = vector<int>;

poly operator+(const poly &a, const poly &b) {
	poly res(a.size());
	rep(i, a.size())res[i] = a[i] + b[i];
	return res;
}
poly operator-(const poly &a) {
	poly res(a.size());
	rep(i, a.size())res[i] = -a[i];
	return res;
}

const poly inf(52, mod);
const poly zerop(52,0);

using speP = pair<poly, int>;

int max_n;
const int mn = 100000;
struct edge {
	int to, cap; poly cost; int rev;
};
vector<edge> G[mn];
P par[mn];
poly dist[mn];
void add_edge(int from, int to, int cap, poly cost) {
	G[from].push_back({ to,cap,cost,(int)G[to].size() });
	G[to].push_back({ from,0,-cost,(int)G[from].size() - 1 });
	max_n = max({ max_n, from + 1, to + 1 });
}
poly minimum_road(int s, int t) {
	fill(par, par + max_n, LDP{ -1,-1 });
	fill(dist, dist + max_n, inf);
	dist[s] = zerop;
	priority_queue<speP, vector<speP>, greater<speP>> q; q.push({ zerop,s });
	while (!q.empty()) {
		speP p = q.top(); q.pop();
		int id = p.second;
		if (id == t)continue;
		if (p.first > dist[id])continue;
		rep(j, G[id].size()) {
			if (G[id][j].cap > 0) {
				int to = G[id][j].to;
				poly nd = p.first + G[id][j].cost;
				if (nd < dist[to]) {
					dist[to] = nd;
					par[to] = { id,j };
					q.push({ dist[to],to });
				}
			}
		}
	}
	int cur = t;
	int f = 1;
	while (cur != s) {
		int p = par[cur].first, j = par[cur].second;
		if (p < 0)return { -1,-1 };
		G[p][j].cap -= f;
		if (G[p][j].rev >= 0) {
			G[cur][G[p][j].rev].cap += f;
		}
		cur = p;
	}
	return dist[t];
}
poly minimum_cost_flow(int s, int t, int k) {
	poly ret = zerop;
	rep(i, k) {
		poly z = minimum_road(s, t);
		ret = ret + z;
	}
	return ret;
}

int trans(char &t) {
	if (t <= 'Z')return t - 'A';
	else return 26 + t - 'a';
}
char trans(int &i) {
	if (i < 26)return 'A' + i;
	else return 'a' + i - 26;
}
char mp[50][50];
void solve() {
	int n; cin >> n;
	rep(i, n) {
		rep(j, n) {
			cin >> mp[i][j];
		}
	}
	int sta = 2 * n; int goa = sta + 1;
	rep(i, n)add_edge(sta, i, 1, zerop);
	rep(i, n)add_edge(i + n, goa, 1, zerop);
	rep(i, n)rep(j, n) {
		poly c(52, 52); c[trans(mp[i][j])]--;
		add_edge(i, j + n, 1, c);
	}
	string ans;
	poly p=minimum_cost_flow(sta, goa, n);
	rep(i, 52)rep(j, 52*n-p[i])ans.push_back(trans(i));
	cout << ans << endl;
}

signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	//cout << fixed << setprecision(12);
	//init_f();
	//int t; cin >> t; rep(i, t)solve();
	solve();
	stop
		return 0;
}
accuracy: 1
time_ms: 20
memory_kb: 9320
final_rank: 9

code_snippet: #include<iostream>
#include<string>
#include<cstdio>
#include<vector>
#include<cmath>
#include<algorithm>
#include<functional>
#include<iomanip>
#include<queue>
#include<ciso646>
#include<random>
#include<map>
#include<set>
#include<bitset>
#include<stack>
#include<unordered_map>
#include<utility>
#include<cassert>
#include<complex>
using namespace std;

//#define int long long
typedef long long ll;

typedef unsigned long long ul;
typedef unsigned int ui;
const ll mod = 1000000007;
const ll INF = (1e+18) + 7;
typedef pair<int, int>P;
#define stop char nyaa;cin>>nyaa;
#define rep(i,n) for(int i=0;i<n;i++)
#define per(i,n) for(int i=n-1;i>=0;i--)
#define Rep(i,sta,n) for(int i=sta;i<n;i++)
#define rep1(i,n) for(int i=1;i<=n;i++)
#define per1(i,n) for(int i=n;i>=1;i--)
#define Rep1(i,sta,n) for(int i=sta;i<=n;i++)
#define all(v) (v).begin(),(v).end()
typedef pair<ll, ll> LP;
typedef long double ld;
typedef pair<ld, ld> LDP;
const ld eps = 1e-6;
const ld pi = acos(-1.0);
//typedef vector<vector<ll>> mat;
typedef vector<int> vec;

ll mod_pow(ll a, ll n) {
	ll res = 1;
	while (n) {
		if (n & 1)res = res * a%mod;
		a = a * a%mod; n >>= 1;
	}
	return res;
}

struct modint {
	ll n;
	modint() :n(0) { ; }
	modint(ll m) :n(m) {
		if (n >= mod)n %= mod;
		else if (n < 0)n = (n%mod + mod) % mod;
	}
	operator int() { return n; }
};
bool operator==(modint a, modint b) { return a.n == b.n; }
modint operator+=(modint &a, modint b) { a.n += b.n; if (a.n >= mod)a.n -= mod; return a; }
modint operator-=(modint &a, modint b) { a.n -= b.n; if (a.n < 0)a.n += mod; return a; }
modint operator*=(modint &a, modint b) { a.n = ((ll)a.n*b.n) % mod; return a; }
modint operator+(modint a, modint b) { return a += b; }
modint operator-(modint a, modint b) { return a -= b; }
modint operator*(modint a, modint b) { return a *= b; }
modint operator^(modint a, int n) {
	if (n == 0)return modint(1);
	modint res = (a*a) ^ (n / 2);
	if (n % 2)res = res * a;
	return res;
}

ll inv(ll a, ll p) {
	return (a == 1 ? 1 : (1 - p * inv(p%a, a)) / a + p);
}
modint operator/(modint a, modint b) { return a * modint(inv(b, mod)); }

const int max_n = 1 << 22;
modint fact[max_n], factinv[max_n];
void init_f() {
	fact[0] = modint(1);
	for (int i = 0; i < max_n - 1; i++) {
		fact[i + 1] = fact[i] * modint(i + 1);
	}
	factinv[max_n - 1] = modint(1) / fact[max_n - 1];
	for (int i = max_n - 2; i >= 0; i--) {
		factinv[i] = factinv[i + 1] * modint(i + 1);
	}
}
modint comb(int a, int b) {
	if (a < 0 || b < 0 || a < b)return 0;
	return fact[a] * factinv[b] * factinv[a - b];
}


int dx[4] = { 1,0,-1,0 };
int dy[4] = { 0,1,0,-1 };

int trans(char &t) {
	if (t <= 'Z')return t - 'A';
	else return 26 + t - 'a';
}
char trans(int &i) {
	if (i < 26)return 'A' + i;
	else return 'a' + (i - 26);
}

#include<cstring>
struct edge { int to, cap, rev; };
vector<edge> G[500];
bool used[500];
void add_edge(int from, int to, int cap) {
	G[from].push_back(edge{ to, cap, (int)G[to].size() });
	G[to].push_back(edge{ from, 0, (int)G[from].size() - 1 });
}
int dfs(int v, int t, int f) {
	if (v == t)return f;
	used[v] = true;
	for (int i = 0; i < (int)G[v].size(); i++) {
		edge &e = G[v][i];
		if (!used[e.to] && e.cap > 0) {
			int d = dfs(e.to, t, min(f, e.cap));
			if (d > 0) {
				e.cap -= d;
				G[e.to][e.rev].cap += d;
				return d;
			}
		}
	}
	return 0;
}
int max_flow(int s, int t) {
	int flow = 0;
	for (;;) {
		memset(used, 0, sizeof(used));
		int f = dfs(s, t, mod);
		if (f == 0)return flow;
		flow += f;
	}
}
int n;
char mp[50][50];

vector<edge> cop[500];

int cnt(int s) {
	rep(i, 2 * n + 2)cop[i] = G[i];
	vector<P> v;
	rep(i, n)rep(j, n)if (trans(mp[i][j]) == s) {
		add_edge(i, j + n, 1);
		int f = max_flow(2*n, 2*n+1);
		if (f) {
			v.push_back({ i,j });
		}
		rep(k, 2 * n + 2)G[k] = cop[k];
	}
	rep(j, v.size()) {
		add_edge(v[j].first, v[j].second + n, 1);
	}
	return max_flow(2 * n, 2 * n + 1);
}

void solve() {
	cin >> n;
	rep(i, n)rep(j, n)cin >> mp[i][j];
	int sta = 2 * n; int goa = sta + 1;
	rep(i, n)add_edge(sta, i, 1);
	rep(i, n)add_edge(i + n, goa, 1);


	string ans;
	rep(i, 52) {
		int c = cnt(i);
		rep(j, c)ans.push_back((char)trans(i));
	}
	cout << ans << endl;
}

signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	//cout << fixed << setprecision(12);
	//init_f();
	//int t; cin >> t; rep(i, t)solve();
	solve();
	stop
		return 0;
}
accuracy: 0.3488372093023256
time_ms: 10
memory_kb: 68856
final_rank: 15

code_snippet: #include<iostream>
#include<string>
#include<cstdio>
#include<vector>
#include<cmath>
#include<algorithm>
#include<functional>
#include<iomanip>
#include<queue>
#include<ciso646>
#include<random>
#include<map>
#include<set>
#include<bitset>
#include<stack>
#include<unordered_map>
#include<utility>
#include<cassert>
#include<complex>
using namespace std;

//#define int long long
typedef long long ll;

typedef unsigned long long ul;
typedef unsigned int ui;
const ll mod = 1000000007;
const ll INF = (1e+18) + 7;
typedef pair<int, int>P;
#define stop char nyaa;cin>>nyaa;
#define rep(i,n) for(int i=0;i<n;i++)
#define per(i,n) for(int i=n-1;i>=0;i--)
#define Rep(i,sta,n) for(int i=sta;i<n;i++)
#define rep1(i,n) for(int i=1;i<=n;i++)
#define per1(i,n) for(int i=n;i>=1;i--)
#define Rep1(i,sta,n) for(int i=sta;i<=n;i++)
#define all(v) (v).begin(),(v).end()
typedef pair<ll, ll> LP;
typedef long double ld;
typedef pair<ld, ld> LDP;
const ld eps = 1e-6;
const ld pi = acos(-1.0);
//typedef vector<vector<ll>> mat;
typedef vector<int> vec;

ll mod_pow(ll a, ll n) {
	ll res = 1;
	while (n) {
		if (n & 1)res = res * a%mod;
		a = a * a%mod; n >>= 1;
	}
	return res;
}

struct modint {
	ll n;
	modint() :n(0) { ; }
	modint(ll m) :n(m) {
		if (n >= mod)n %= mod;
		else if (n < 0)n = (n%mod + mod) % mod;
	}
	operator int() { return n; }
};
bool operator==(modint a, modint b) { return a.n == b.n; }
modint operator+=(modint &a, modint b) { a.n += b.n; if (a.n >= mod)a.n -= mod; return a; }
modint operator-=(modint &a, modint b) { a.n -= b.n; if (a.n < 0)a.n += mod; return a; }
modint operator*=(modint &a, modint b) { a.n = ((ll)a.n*b.n) % mod; return a; }
modint operator+(modint a, modint b) { return a += b; }
modint operator-(modint a, modint b) { return a -= b; }
modint operator*(modint a, modint b) { return a *= b; }
modint operator^(modint a, int n) {
	if (n == 0)return modint(1);
	modint res = (a*a) ^ (n / 2);
	if (n % 2)res = res * a;
	return res;
}

ll inv(ll a, ll p) {
	return (a == 1 ? 1 : (1 - p * inv(p%a, a)) / a + p);
}
modint operator/(modint a, modint b) { return a * modint(inv(b, mod)); }

const int max_n = 1 << 22;
modint fact[max_n], factinv[max_n];
void init_f() {
	fact[0] = modint(1);
	for (int i = 0; i < max_n - 1; i++) {
		fact[i + 1] = fact[i] * modint(i + 1);
	}
	factinv[max_n - 1] = modint(1) / fact[max_n - 1];
	for (int i = max_n - 2; i >= 0; i--) {
		factinv[i] = factinv[i + 1] * modint(i + 1);
	}
}
modint comb(int a, int b) {
	if (a < 0 || b < 0 || a < b)return 0;
	return fact[a] * factinv[b] * factinv[a - b];
}


int dx[4] = { 1,0,-1,0 };
int dy[4] = { 0,1,0,-1 };

int trans(char &t) {
	if (t <= 'Z')return t - 'A';
	else return 26 + t - 'a';
}
char trans(int &i) {
	if (i < 26)return 'A' + i;
	else return 'a' + (i - 26);
}

#include<cstring>
struct edge { int to, cap, rev; };
vector<edge> G[500];
bool used[500];
void add_edge(int from, int to, int cap) {
	G[from].push_back(edge{ to, cap, (int)G[to].size() });
	G[to].push_back(edge{ from, 0, (int)G[from].size() - 1 });
}
int dfs(int v, int t, int f) {
	if (v == t)return f;
	used[v] = true;
	for (int i = 0; i < (int)G[v].size(); i++) {
		edge &e = G[v][i];
		if (!used[e.to] && e.cap > 0) {
			int d = dfs(e.to, t, min(f, e.cap));
			if (d > 0) {
				e.cap -= d;
				G[e.to][e.rev].cap += d;
				return d;
			}
		}
	}
	return 0;
}
int max_flow(int s, int t) {
	int flow = 0;
	for (;;) {
		memset(used, 0, sizeof(used));
		int f = dfs(s, t, mod);
		if (f == 0)return flow;
		flow += f;
	}
}
int n;
char mp[50][50];

void solve() {
	cin >> n;
	rep(i, n)rep(j, n)cin >> mp[i][j];
	vector<pair<char, P>> v;
	rep(i, n)rep(j, n) {
		v.push_back({ mp[i][j],{i,j} });
	}
	sort(v.begin(), v.end());
	int sta = 2 * n; int goa = sta + 1;
	rep(i, n)add_edge(sta, i, 1);
	rep(i, n)add_edge(i + n, goa, 1);
	string ans;
	rep(i, n*n) {
		char c = v[i].first;
		int x = v[i].second.first;
		int y = v[i].second.second;
		add_edge(x, n + y, 1);
		int f = max_flow(sta, goa);
		if (f) {
			ans.push_back(c);
		}
		else {
			G[x].pop_back();
			G[n+y].pop_back();
		}
	}
	cout << ans << endl;
}

signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	//cout << fixed << setprecision(12);
	//init_f();
	//int t; cin >> t; rep(i, t)solve();
	solve();
	stop
		return 0;
}
accuracy: 0.023255813953488372
time_ms: 10
memory_kb: 68768
final_rank: 20

code_snippet: #include<iostream>
#include<string>
#include<cstdio>
#include<vector>
#include<cmath>
#include<algorithm>
#include<functional>
#include<iomanip>
#include<queue>
#include<ciso646>
#include<random>
#include<map>
#include<set>
#include<bitset>
#include<stack>
#include<unordered_map>
#include<utility>
#include<cassert>
#include<complex>
using namespace std;

//#define int long long
typedef long long ll;

typedef unsigned long long ul;
typedef unsigned int ui;
const ll mod = 1000000007;
const ll INF = (1e+18) + 7;
typedef pair<int, int>P;
#define stop char nyaa;cin>>nyaa;
#define rep(i,n) for(int i=0;i<n;i++)
#define per(i,n) for(int i=n-1;i>=0;i--)
#define Rep(i,sta,n) for(int i=sta;i<n;i++)
#define rep1(i,n) for(int i=1;i<=n;i++)
#define per1(i,n) for(int i=n;i>=1;i--)
#define Rep1(i,sta,n) for(int i=sta;i<=n;i++)
#define all(v) (v).begin(),(v).end()
typedef pair<ll, ll> LP;
typedef long double ld;
typedef pair<ld, ld> LDP;
const ld eps = 1e-6;
const ld pi = acos(-1.0);
//typedef vector<vector<ll>> mat;
typedef vector<int> vec;

ll mod_pow(ll a, ll n) {
	ll res = 1;
	while (n) {
		if (n & 1)res = res * a%mod;
		a = a * a%mod; n >>= 1;
	}
	return res;
}

struct modint {
	ll n;
	modint() :n(0) { ; }
	modint(ll m) :n(m) {
		if (n >= mod)n %= mod;
		else if (n < 0)n = (n%mod + mod) % mod;
	}
	operator int() { return n; }
};
bool operator==(modint a, modint b) { return a.n == b.n; }
modint operator+=(modint &a, modint b) { a.n += b.n; if (a.n >= mod)a.n -= mod; return a; }
modint operator-=(modint &a, modint b) { a.n -= b.n; if (a.n < 0)a.n += mod; return a; }
modint operator*=(modint &a, modint b) { a.n = ((ll)a.n*b.n) % mod; return a; }
modint operator+(modint a, modint b) { return a += b; }
modint operator-(modint a, modint b) { return a -= b; }
modint operator*(modint a, modint b) { return a *= b; }
modint operator^(modint a, int n) {
	if (n == 0)return modint(1);
	modint res = (a*a) ^ (n / 2);
	if (n % 2)res = res * a;
	return res;
}

ll inv(ll a, ll p) {
	return (a == 1 ? 1 : (1 - p * inv(p%a, a)) / a + p);
}
modint operator/(modint a, modint b) { return a * modint(inv(b, mod)); }

const int max_n = 1 << 22;
modint fact[max_n], factinv[max_n];
void init_f() {
	fact[0] = modint(1);
	for (int i = 0; i < max_n - 1; i++) {
		fact[i + 1] = fact[i] * modint(i + 1);
	}
	factinv[max_n - 1] = modint(1) / fact[max_n - 1];
	for (int i = max_n - 2; i >= 0; i--) {
		factinv[i] = factinv[i + 1] * modint(i + 1);
	}
}
modint comb(int a, int b) {
	if (a < 0 || b < 0 || a < b)return 0;
	return fact[a] * factinv[b] * factinv[a - b];
}


int dx[4] = { 1,0,-1,0 };
int dy[4] = { 0,1,0,-1 };

int trans(char &t) {
	if (t <= 'Z')return t - 'A';
	else return 26 + t - 'a';
}
char trans(int &i) {
	if (i < 26)return 'A' + i;
	else return 'a' + (i - 26);
}

#include<cstring>
struct edge { int to, cap, rev; };
vector<edge> G[500];
bool used[500];
void add_edge(int from, int to, int cap) {
	G[from].push_back(edge{ to, cap, (int)G[to].size() });
	G[to].push_back(edge{ from, 0, (int)G[from].size() - 1 });
}
int dfs(int v, int t, int f) {
	if (v == t)return f;
	used[v] = true;
	for (int i = 0; i < (int)G[v].size(); i++) {
		edge &e = G[v][i];
		if (!used[e.to] && e.cap > 0) {
			int d = dfs(e.to, t, min(f, e.cap));
			if (d > 0) {
				e.cap -= d;
				G[e.to][e.rev].cap += d;
				return d;
			}
		}
	}
	return 0;
}
int max_flow(int s, int t) {
	int flow = 0;
	for (;;) {
		memset(used, 0, sizeof(used));
		int f = dfs(s, t, mod);
		if (f == 0)return flow;
		flow += f;
	}
}
int n;
char mp[50][50];

bool ban[52];

int cnt(int s) {
	rep(i, 2 * n + 2)G[i].clear();
	rep(i, n)rep(j, n)if (trans(mp[i][j]) <= s) {
		if (ban[trans(mp[i][j])])continue;
		add_edge(i, j + n, 1);
	}
	int sta = 2 * n; int goa = sta + 1;
	rep(i, n)add_edge(sta, i, 1);
	rep(i, n)add_edge(i + n, goa, 1);
	return max_flow(sta, goa);
}
void solve() {
	cin >> n;
	rep(i, n)rep(j, n)cin >> mp[i][j];
	int las = 0;
	string ans;
	rep(i, 52) {
		int c = cnt(i);
		if (c == las)ban[i] = true;
		rep(j, c - las) {
			ans.push_back(trans(i));
		}
		//cout << c << " " << char(trans(i)) << endl;
		las = c;
	}
	cout << ans << endl;
}

signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	//cout << fixed << setprecision(12);
	//init_f();
	//int t; cin >> t; rep(i, t)solve();
	solve();
	stop
		return 0;
}
accuracy: 0.09302325581395349
time_ms: 10
memory_kb: 68812
final_rank: 17

code_snippet: #include<iostream>
#include<string>
#include<cstdio>
#include<vector>
#include<cmath>
#include<algorithm>
#include<functional>
#include<iomanip>
#include<queue>
#include<ciso646>
#include<random>
#include<map>
#include<set>
#include<bitset>
#include<stack>
#include<unordered_map>
#include<utility>
#include<cassert>
#include<complex>
using namespace std;

//#define int long long
typedef long long ll;

typedef unsigned long long ul;
typedef unsigned int ui;
const ll mod = 1000000007;
const ll INF = (1e+18) + 7;
typedef pair<int, int>P;
#define stop char nyaa;cin>>nyaa;
#define rep(i,n) for(int i=0;i<n;i++)
#define per(i,n) for(int i=n-1;i>=0;i--)
#define Rep(i,sta,n) for(int i=sta;i<n;i++)
#define rep1(i,n) for(int i=1;i<=n;i++)
#define per1(i,n) for(int i=n;i>=1;i--)
#define Rep1(i,sta,n) for(int i=sta;i<=n;i++)
#define all(v) (v).begin(),(v).end()
typedef pair<ll, ll> LP;
typedef long double ld;
typedef pair<ld, ld> LDP;
const ld eps = 1e-6;
const ld pi = acos(-1.0);
//typedef vector<vector<ll>> mat;
typedef vector<int> vec;

ll mod_pow(ll a, ll n) {
	ll res = 1;
	while (n) {
		if (n & 1)res = res * a%mod;
		a = a * a%mod; n >>= 1;
	}
	return res;
}

struct modint {
	ll n;
	modint() :n(0) { ; }
	modint(ll m) :n(m) {
		if (n >= mod)n %= mod;
		else if (n < 0)n = (n%mod + mod) % mod;
	}
	operator int() { return n; }
};
bool operator==(modint a, modint b) { return a.n == b.n; }
modint operator+=(modint &a, modint b) { a.n += b.n; if (a.n >= mod)a.n -= mod; return a; }
modint operator-=(modint &a, modint b) { a.n -= b.n; if (a.n < 0)a.n += mod; return a; }
modint operator*=(modint &a, modint b) { a.n = ((ll)a.n*b.n) % mod; return a; }
modint operator+(modint a, modint b) { return a += b; }
modint operator-(modint a, modint b) { return a -= b; }
modint operator*(modint a, modint b) { return a *= b; }
modint operator^(modint a, int n) {
	if (n == 0)return modint(1);
	modint res = (a*a) ^ (n / 2);
	if (n % 2)res = res * a;
	return res;
}

ll inv(ll a, ll p) {
	return (a == 1 ? 1 : (1 - p * inv(p%a, a)) / a + p);
}
modint operator/(modint a, modint b) { return a * modint(inv(b, mod)); }

const int max_n = 1 << 22;
modint fact[max_n], factinv[max_n];
void init_f() {
	fact[0] = modint(1);
	for (int i = 0; i < max_n - 1; i++) {
		fact[i + 1] = fact[i] * modint(i + 1);
	}
	factinv[max_n - 1] = modint(1) / fact[max_n - 1];
	for (int i = max_n - 2; i >= 0; i--) {
		factinv[i] = factinv[i + 1] * modint(i + 1);
	}
}
modint comb(int a, int b) {
	if (a < 0 || b < 0 || a < b)return 0;
	return fact[a] * factinv[b] * factinv[a - b];
}


int dx[4] = { 1,0,-1,0 };
int dy[4] = { 0,1,0,-1 };

int trans(char &t) {
	if (t <= 'Z')return t - 'A';
	return 26 + t - 'a';
}
char trans(int &i) {
	if (i < 26)return 'A' + i;
	else return 'a' + (i - 26);
}

#include<cstring>
struct edge { int to, cap, rev; };
vector<edge> G[500];
bool used[500];
void add_edge(int from, int to, int cap) {
	G[from].push_back(edge{ to, cap, (int)G[to].size() });
	G[to].push_back(edge{ from, 0, (int)G[from].size() - 1 });
}
int dfs(int v, int t, int f) {
	if (v == t)return f;
	used[v] = true;
	for (int i = 0; i < (int)G[v].size(); i++) {
		edge &e = G[v][i];
		if (!used[e.to] && e.cap > 0) {
			int d = dfs(e.to, t, min(f, e.cap));
			if (d > 0) {
				e.cap -= d;
				G[e.to][e.rev].cap += d;
				return d;
			}
		}
	}
	return 0;
}
int max_flow(int s, int t) {
	int flow = 0;
	for (;;) {
		memset(used, 0, sizeof(used));
		int f = dfs(s, t, mod);
		if (f == 0)return flow;
		flow += f;
	}
}
int n;
char mp[50][50];

int cnt(int s) {
	rep(i, 2 * n + 2)G[i].clear();
	rep(i, n)rep(j, n)if (trans(mp[i][j]) <= s) {
		add_edge(i, j + n, 1);
	}
	int sta = 2 * n; int goa = sta + 1;
	rep(i, n)add_edge(sta, i, 1);
	rep(i, n)add_edge(i + n, goa, 1);
	return max_flow(sta, goa);
}
void solve() {
	cin >> n;
	rep(i, n)rep(j, n)cin >> mp[i][j];
	int las = 0;
	string ans;
	rep(i, 52) {
		int c = cnt(i);
		rep(j, c - las) {
			ans.push_back(trans(i));
		}
		las = c;
	}
	cout << ans << endl;
}

signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	//cout << fixed << setprecision(12);
	//init_f();
	//int t; cin >> t; rep(i, t)solve();
	solve();
	stop
		return 0;
}
accuracy: 0.06976744186046512
time_ms: 10
memory_kb: 68728
final_rank: 19

code_snippet: #define _USE_MATH_DEFINES
#include <bits/stdc++.h>
using namespace std;
#define FOR(i,m,n) for(int i=(m);i<(n);++i)
#define REP(i,n) FOR(i,0,n)
#define ALL(v) (v).begin(),(v).end()
using ll = long long;
template <typename T> using posteriority_queue = priority_queue<T, vector<T>, greater<T> >;
const int INF = 0x3f3f3f3f;
const ll LINF = 0x3f3f3f3f3f3f3f3fLL;
const double EPS = 1e-8;
const int MOD = 1000000007;
// const int MOD = 998244353;
const int dy[] = {1, 0, -1, 0}, dx[] = {0, -1, 0, 1};
// const int dy[] = {1, 1, 0, -1, -1, -1, 0, 1}, dx[] = {0, -1, -1, -1, 0, 1, 1, 1};
template <typename T, typename U> inline bool chmax(T &a, U b) { return a < b ? (a = b, true) : false; }
template <typename T, typename U> inline bool chmin(T &a, U b) { return a > b ? (a = b, true) : false; }
template <typename T> void unique(vector<T> &a) { a.erase(unique(ALL(a)), a.end()); }
struct IOSetup {
  IOSetup() {
    cin.tie(nullptr);
    ios_base::sync_with_stdio(false);
    cout << fixed << setprecision(20);
  }
} iosetup;

// "base" must be a power of 10.
const int lg10_base = 9, base = static_cast<int>(round(pow(10, lg10_base)));
struct BigInt {
  int sign; vector<int> dat;
  BigInt(ll val = 0) { *this = val; }
  BigInt(const string &s) { *this = s; }
  vector<ll> convert_base(int new_lg10_base, ll new_base) const {
    // assert(new_base == static_cast<int>(round(pow(10, new_lg10_base))))
    int mx_base = max(lg10_base, new_lg10_base);
    vector<ll> p(mx_base + 1, 1);
    FOR(i, 1, mx_base + 1) p[i] = p[i - 1] * 10;
    vector<ll> res;
    ll now_val = 0;
    int now_lg10_base = 0;
    for (int e : dat) {
      now_val += p[now_lg10_base] * e;
      now_lg10_base += lg10_base;
      while (now_lg10_base >= new_lg10_base) {
        res.emplace_back(now_val % new_base);
        now_val /= new_base;
        now_lg10_base -= new_lg10_base;
      }
    }
    res.emplace_back(now_val);
    while (!res.empty() && res.back() == 0) res.pop_back();
    return res;
  }
  int digit_sum() {
    assert(sign == 1);
    string s = to_string();
    int res = 0;
    for (char c : s) res += c - '0';
    return res;
  }
  int length() {
    if (dat.empty()) return 1;
    int res = lg10_base * (dat.size() - 1);
    int tmp = dat.back();
    while (tmp > 0) { ++res; tmp /= 10; }
    return res;
  }
  BigInt pow(BigInt exponent) {
    BigInt res = 1, tmp = *this;
    while (exponent > 0) {
      if (exponent.dat[0] & 1) res *= tmp;
      tmp *= tmp;
      exponent /= 2;
    }
    return res;
  }
  ll to_llong() const {
    assert(*this >= LLONG_MIN && *this <= LLONG_MAX);
    ll res = 0;
    for (int i = static_cast<int>(dat.size()) - 1; i >= 0; --i) (res *= base) += dat[i];
    return res;
  }
  string to_string() { stringstream ss; ss << *this; string res; ss >> res; return res; }
  void trim() {
    while (!dat.empty() && dat.back() == 0) dat.pop_back();
    if (dat.empty()) sign = 1;
  }
  BigInt &operator=(ll val) {
    sign = 1;
    if (val < 0) { sign = -1; val *= -1;}
    dat.clear();
    for (; val > 0; val /= base) dat.emplace_back(val % base);
    return *this;
  }
  BigInt &operator=(const string &s) {
    sign = 1;
    dat.clear();
    if (!s.empty()) {
      int tail = 0;
      if (s[tail] == '-' || s[tail] == '+') {
        if (s[tail] == '-') sign = -1;
        ++tail;
      }
      for (int i = s.length() - 1; i >= tail; i -= lg10_base) {
        int val = 0;
        for (int j = max(tail, i - lg10_base + 1); j <= i; ++j) val = val * 10 + (s[j] - '0');
        dat.emplace_back(val);
      }
    }
    trim();
    return *this;
  }
  BigInt &operator=(const BigInt &x) {
    sign = x.sign;
    dat.resize(x.dat.size());
    copy(ALL(x.dat), dat.begin());
    return *this;
  }
  BigInt &operator+=(const BigInt &x) {
    if (sign == x.sign) {
      bool carry = false;
      for (int i = 0; i < max(dat.size(), x.dat.size()) || carry; ++i) {
        if (i == dat.size()) dat.emplace_back(0);
        dat[i] += (i < x.dat.size() ? x.dat[i] : 0) + carry;
        carry = dat[i] >= base;
        if (carry) dat[i] -= base;
      }
    } else {
      *this -= -x;
    }
    return *this;
  }
  BigInt &operator-=(const BigInt &x) {
    if (sign == x.sign) {
      BigInt abs_this = *this, abs_x = x;
      abs_this.sign = 1; abs_x.sign = 1;
      if (abs_this >= abs_x) {
        bool carry = false;
        for (int i = 0; i < dat.size() || carry; ++i) {
          dat[i] -= (i < x.dat.size() ? x.dat[i] : 0) + carry;
          carry = dat[i] < 0;
          if (carry) dat[i] += base;
        }
        trim();
      } else {
        *this = -(x - *this);
      }
    } else {
      *this += -x;
    }
    return *this;
  }
  BigInt &operator*=(const BigInt &x) {
    const int new_log10_base = 6, new_base = 1000000;
    vector<ll> this6 = convert_base(new_log10_base, new_base), x6 = x.convert_base(new_log10_base, new_base);
    vector<ll> res = karatsuba(this6, 0, this6.size(), x6, 0, x6.size());
    REP(i, res.size()) {
      ll quo = res[i] / new_base;
      if (quo > 0) {
        if (i + 1 == res.size()) res.emplace_back(0);
        res[i + 1] += quo;
      }
      res[i] %= new_base;
    }
    string s = (sign * x.sign == 1 ? "+" : "-");
    for (int i = static_cast<int>(res.size()) - 1; i >= 0; --i) {
      string tmp = std::to_string(res[i]);
      REP(_, new_log10_base - tmp.size()) s += '0';
      s += tmp;
    }
    return *this = s;
  }
  BigInt &operator/=(int x) { return *this = divide(x).first; }
  BigInt &operator/=(const BigInt &x) { return *this = divide(x).first; }
  BigInt &operator%=(int x) { return *this = divide(x).second; }
  BigInt &operator%=(const BigInt &x) { return *this = divide(x).second; }
  bool operator==(const BigInt &x) const {
    if (sign != x.sign || dat.size() != x.dat.size()) return false;
    int sz = dat.size();
    REP(i, sz) if (dat[i] != x.dat[i]) return false;
    return true;
  }
  bool operator!=(const BigInt &x) const { return !(*this == x); }
  bool operator<(const BigInt &x) const {
    if (sign != x.sign) return sign < x.sign;
    if (dat.size() != x.dat.size()) return sign * dat.size() < x.sign * x.dat.size();
    for (int i = static_cast<int>(dat.size()) - 1; i >= 0; --i) {
      if (dat[i] != x.dat[i]) return dat[i] * sign < x.dat[i] * x.sign;
    }
    return false;
  }
  bool operator<=(const BigInt &x) const { return !(x < *this); }
  bool operator>(const BigInt &x) const { return x < *this; }
  bool operator>=(const BigInt &x) const { return !(*this < x); }
  BigInt &operator++() { return *this += 1; }
  BigInt operator++(int) { BigInt res = *this; ++*this; return res; }
  BigInt &operator--() { return *this -= 1; }
  BigInt operator--(int) { BigInt res = *this; --*this; return res; }
  BigInt operator+() const { return *this; }
  BigInt operator-() const { BigInt res = *this; res.sign *= -1; return res; }
  BigInt operator+(const BigInt &x) const { return BigInt(*this) += x; }
  BigInt operator-(const BigInt &x) const { return BigInt(*this) -= x; }
  BigInt operator*(const BigInt &x) const { return BigInt(*this) *= x; }
  BigInt operator/(int x) const { return BigInt(*this) /= x; }
  BigInt operator/(const BigInt &x) const { return BigInt(*this) /= x; }
  BigInt operator%(int x) const { return BigInt(*this) %= x; }
  BigInt operator%(const BigInt &x) const { return BigInt(*this) %= x; }
  friend ostream &operator<<(ostream &os, const BigInt &x) {
    if (x.sign == -1) os << '-';
    os << (x.dat.empty() ? 0 : x.dat.back());
    for (int i = static_cast<int>(x.dat.size()) - 2; i >= 0; --i) os << setw(lg10_base) << setfill('0') << x.dat[i];
    return os;
  }
  friend istream &operator>>(istream &is, BigInt &x) { string s; is >> s; x = s; return is; }
private:
  vector<ll> karatsuba(vector<ll> &a, int a_l, int a_r, vector<ll> &b, int b_l, int b_r) {
    int a_len = a_r - a_l, b_len = b_r - b_l;
    if (a_len < b_len) return karatsuba(b, b_l, b_r, a, a_l, a_r);
    vector<ll> res(a_len + b_len, 0);
    if (b_len <= 32) {
      FOR(i, a_l, a_r) FOR(j, b_l, b_r) res[(i - a_l) + (j - b_l)] += a[i] * b[j];
    } else {
      int mid = (a_len + 1) / 2, n = min(a_len, mid);
      for (int i = a_l; i + mid < a_r; ++i) a[i] += a[i + mid];
      for (int i = b_l; i + mid < b_r; ++i) b[i] += b[i + mid];
      vector<ll> tmp = karatsuba(a, a_l, a_l + mid, b, b_l, b_l + n);
      REP(i, tmp.size()) res[mid + i] = tmp[i];
      for (int i = a_l; i + mid < a_r; ++i) a[i] -= a[i + mid];
      for (int i = b_l; i + mid < b_r; ++i) b[i] -= b[i + mid];
      tmp = karatsuba(a, a_l, a_l + mid, b, b_l, b_l + n);
      REP(i, tmp.size()) { res[i] += tmp[i]; res[mid + i] -= tmp[i]; }
      tmp = karatsuba(a, a_l + mid, a_r, b, b_l + n, b_r);
      REP(i, tmp.size()) { res[mid + i] -= tmp[i]; res[(mid << 1) + i] += tmp[i]; }
    }
    while (!res.empty() && res.back() == 0) res.pop_back();
    return res;
  }
  pair<BigInt, int> divide(int x) {
    // assert(!x.dat.empty());
    BigInt dividend = *this;
    if (x < 0) { dividend.sign *= -1; x *= -1; }
    ll rem = 0;
    for (int i = static_cast<int>(dividend.dat.size()) - 1; i >= 0; --i) {
      ll tmp = rem * base + dividend.dat[i];
      dividend.dat[i] = static_cast<int>(tmp / x);
      rem = tmp % x;
    }
    dividend.trim();
    return {dividend, static_cast<int>(rem)};
  }
  pair<BigInt, BigInt> divide(const BigInt &x) {
    // assert(!x.dat.empty());
    int k = base / (x.dat.back() + 1);
    BigInt dividend = *this, divisor = x;
    dividend.sign = 1; divisor.sign = 1;
    dividend *= k; divisor *= k;
    BigInt quo, rem = 0;
    quo.dat.resize(dividend.dat.size());
    int sz = divisor.dat.size();
    for (int i = static_cast<int>(dividend.dat.size()) - 1; i >= 0; --i) {
      rem.dat.insert(rem.dat.begin(), dividend.dat[i]);
      quo.dat[i] = static_cast<int>(((sz < rem.dat.size() ? 1LL * rem.dat[sz] * base : 0) + (sz - 1 < rem.dat.size() ? rem.dat[sz - 1] : 0)) / divisor.dat.back());
      rem -= divisor * quo.dat[i];
      while (rem.sign == -1) { rem += divisor; --quo.dat[i];  }
    }
    quo.sign = sign * x.sign; rem.sign = sign;
    quo.trim(); rem.trim();
    return {quo, rem / k};
  }
};
BigInt __gcd(BigInt a, BigInt b) { while (!b.dat.empty()) swap(a %= b, b); return a; }
BigInt __lcm(const BigInt &a, const BigInt &b) { return a / __gcd(a, b) * b; }
BigInt abs(const BigInt &x) { BigInt res = x; res.sign = 1; return res; }
BigInt max(const BigInt &a, const BigInt &b) { return a < b ? b : a; }
BigInt min(const BigInt &a, const BigInt &b) { return a < b ? a : b; }

template <typename T, typename U>
struct PrimalDual {
  using Pui = pair<U, int>;

  struct Edge {
    int dst, rev;
    T cap;
    U cost;
    Edge(int dst, T cap, U cost, int rev) : dst(dst), cap(cap), cost(cost), rev(rev) {}
  };

  vector<vector<Edge> > graph;

  PrimalDual(int n, const T TINF, const U UINF) : n(n), TINF(TINF), UINF(UINF), graph(n), prev_v(n, -1), prev_e(n, -1), potential(n, 0), dist(n) {}

  void add_edge(int src, int dst, T cap, U cost) {
    has_negative_edge |= cost < 0;
    graph[src].emplace_back(dst, cap, cost, graph[dst].size());
    graph[dst].emplace_back(src, 0, -cost, graph[src].size() - 1);
  }

  U minimum_cost_flow(int s, int t, T flow) {
    U res = 0;
    if (has_negative_edge) {
      bellman_ford(s);
      if (dist[t] == UINF) return UINF;
      res += calc(s, t, flow);
    }
    while (flow > 0) {
      dijkstra(s);
      if (dist[t] == UINF) return UINF;
      res += calc(s, t, flow);
    }
    return res;
  }

  U minimum_cost_flow(int s, int t) {
    U res = 0;
    bellman_ford(s);
    if (potential[t] >= 0 || dist[t] == UINF) return res;
    T tmp = TINF;
    res += calc(s, t, tmp);
    while (true) {
      dijkstra(s);
      if (potential[t] >= 0 || dist[t] == UINF) return res;
      res += calc(s, t, tmp);
    }
  }

  pair<T, U> min_cost_max_flow(int s, int t, T flow) {
    T mx = flow;
    U cost = 0;
    if (has_negative_edge) {
      bellman_ford(s);
      if (dist[t] == UINF) return {mx - flow, cost};
      cost += calc(s, t, flow);
    }
    while (flow > 0) {
      dijkstra(s);
      if (dist[t] == UINF) return {mx - flow, cost};
      cost += calc(s, t, flow);
    }
    return {mx - flow, cost};
  }

private:
  int n;
  const T TINF;
  const U UINF;
  bool has_negative_edge = false;
  vector<int> prev_v, prev_e;
  vector<U> potential, dist;
  priority_queue<Pui, vector<Pui>, greater<Pui> > que;

  void bellman_ford(int s) {
    fill(ALL(dist), UINF);
    dist[s] = 0;
    bool is_updated = true;
    REP(step, n) {
      is_updated = false;
      REP(i, n) if (dist[i] != UINF) {
        REP(j, graph[i].size()) {
          Edge e = graph[i][j];
          if (e.cap > 0 && dist[e.dst] > dist[i] + e.cost) {
            dist[e.dst] = dist[i] + e.cost;
            prev_v[e.dst] = i;
            prev_e[e.dst] = j;
            is_updated = true;
          }
        }
      }
      if (!is_updated) break;
    }
    assert(!is_updated);
    REP(i, n) {
      if (dist[i] != UINF) potential[i] += dist[i];
    }
  }

  void dijkstra(int s) {
    fill(ALL(dist), UINF);
    dist[s] = 0;
    que.emplace(0, s);
    while (!que.empty()) {
      Pui pr = que.top(); que.pop();
      int ver = pr.second;
      if (dist[ver] < pr.first) continue;
      REP(i, graph[ver].size()) {
        Edge e = graph[ver][i];
        U nx = dist[ver] + e.cost + potential[ver] - potential[e.dst];
        if (e.cap > 0 && dist[e.dst] > nx) {
          dist[e.dst] = nx;
          prev_v[e.dst] = ver;
          prev_e[e.dst] = i;
          que.emplace(dist[e.dst], e.dst);
        }
      }
    }
    REP(i, n) {
      if (dist[i] != UINF) potential[i] += dist[i];
    }
  }

  U calc(int s, int t, T &flow) {
    T f = flow;
    for (int v = t; v != s; v = prev_v[v]) f = min(f, graph[prev_v[v]][prev_e[v]].cap);
    flow -= f;
    for (int v = t; v != s; v = prev_v[v]) {
      Edge &e = graph[prev_v[v]][prev_e[v]];
      e.cap -= f;
      graph[v][e.rev].cap += f;
    }
    return potential[t] * f;
  }
};

template <typename T>
struct WeightedBipartiteMatching {
  WeightedBipartiteMatching(int left, int right, T TINF) : left(left), right(right), pd(left + right + 2, INF, TINF) {}

  void add_edge(int src, int dst, T cost) { pd.add_edge(src, left + dst, 1, -cost); }

  T solve() {
    REP(i, left) pd.add_edge(left + right, i, 1, 0);
    REP(i, right) pd.add_edge(left + i, left + right + 1, 1, 0);
    return -pd.min_cost_max_flow(left + right, left + right + 1, left).second;
  }

  vector<int> matching() {
    vector<int> res(left, -1);
    REP(i, left) {
      for (auto e : pd.graph[i]) {
        if (e.cap == 0 && left <= e.dst && e.dst < left + right) {
          res[i] = e.dst - left;
          break;
        }
      }
    }
    return res;
  }

private:
  int left, right;
  PrimalDual<int, T> pd;
};

int main() {
  int n; cin >> n;
  map<char, BigInt> cost;
  BigInt now = 0;
  for (int i = 25; i >= 0; --i) {
    cost['a' + i] = now;
    now = now * 50 + 1;
  }
  for (int i = 25; i >= 0; --i) {
    cost['A' + i] = now;
    now = now * 50 + 1;
  }
  vector<string> c(n); REP(i, n) cin >> c[i];
  WeightedBipartiteMatching<BigInt> wbm(n, n, now);
  REP(i, n) {
    cin >> c[i];
    REP(j, n) wbm.add_edge(i, j, cost[c[i][j]]);
  }
  wbm.solve();
  string ans = "";
  vector<int> matching = wbm.matching();
  REP(i, n) ans += c[i][matching[i]];
  sort(ALL(ans));
  cout << ans << '\n';
  return 0;
}
accuracy: 1
time_ms: 140
memory_kb: 3616
final_rank: 11

code_snippet: #include <algorithm>
#include <cassert>
#include <cmath>
#include <iomanip>
#include <iostream>
#include <map>
#include <queue>
#include <sstream>
#include <string>
#include <utility>
#include <vector>
using namespace std;

#define FOR(i,m,n) for(int i=(m);i<(n);++i)
#define REP(i,n) FOR(i,0,n)
#define ALL(v) (v).begin(),(v).end()

const int INF = 0x3f3f3f3f;

struct IOSetup {
  IOSetup() {
    cin.tie(nullptr);
    ios_base::sync_with_stdio(false);
    cout << fixed << setprecision(20);
    cerr << fixed << setprecision(10);
  }
} iosetup;
/*-------------------------------------------------*/
const int lg10_base = 9, base = static_cast<int>(round(pow(10, lg10_base)));
struct BigInt {
  int sign;
  vector<int> dat;
  BigInt(long long val = 0) { *this = val; }
  BigInt(const string &str) { *this = str; }
  vector<long long> convert_base(int new_lg10_base, long long new_base) const {
    // assert(new_base == static_cast<int>(round(pow(10, new_lg10_base))))
    int mx_base = max(lg10_base, new_lg10_base);
    vector<long long> p(mx_base + 1, 1);
    FOR(i, 1, mx_base + 1) p[i] = p[i - 1] * 10;
    vector<long long> res;
    long long now_val = 0;
    int now_lg10_base = 0;
    for (int e : dat) {
      now_val += p[now_lg10_base] * e;
      now_lg10_base += lg10_base;
      while (now_lg10_base >= new_lg10_base) {
        res.emplace_back(now_val % new_base);
        now_val /= new_base;
        now_lg10_base -= new_lg10_base;
      }
    }
    res.emplace_back(now_val);
    while (!res.empty() && res.back() == 0) res.pop_back();
    return res;
  }
  int digit_sum() {
    assert(sign == 1);
    string str = to_string();
    int res = 0;
    for (char c : str) res += c - '0';
    return res;
  }
  int length() {
    if (dat.empty()) return 1;
    int res = lg10_base * (dat.size() - 1);
    int tmp = dat.back();
    while (tmp > 0) { ++res; tmp /= 10; }
    return res;
  }
  BigInt pow(BigInt exponent) {
    BigInt res = 1, tmp = *this;
    while (exponent > 0) {
      if (exponent.dat[0] & 1) res *= tmp;
      tmp *= tmp;
      exponent /= 2;
    }
    return res;
  }
  long long to_llong() const {
    assert(*this >= INT64_MIN && *this <= INT64_MAX);
    long long res = 0;
    for (int i = static_cast<int>(dat.size()) - 1; i >= 0; --i) (res *= base) += dat[i];
    return res;
  }
  string to_string() { stringstream ss; ss << *this; string res; ss >> res; return res; }
  void trim() {
    while (!dat.empty() && dat.back() == 0) dat.pop_back();
    if (dat.empty()) sign = 1;
  }
  BigInt &operator=(long long val) {
    sign = 1;
    if (val < 0) { sign = -1; val *= -1;}
    dat.clear();
    for (; val > 0; val /= base) dat.emplace_back(val % base);
    return *this;
  }
  BigInt &operator=(const string &str) {
    sign = 1;
    dat.clear();
    if (!str.empty()) {
      int tail = 0;
      if (str[tail] == '-' || str[tail] == '+') {
        if (str[tail] == '-') sign = -1;
        ++tail;
      }
      for (int i = str.length() - 1; i >= tail; i -= lg10_base) {
        int val = 0;
        for (int j = max(tail, i - lg10_base + 1); j <= i; ++j) val = val * 10 + (str[j] - '0');
        dat.emplace_back(val);
      }
    }
    trim();
    return *this;
  }
  BigInt &operator=(const BigInt &rhs) {
    sign = rhs.sign;
    dat.resize(rhs.dat.size());
    copy(ALL(rhs.dat), dat.begin());
    return *this;
  }
  BigInt &operator+=(const BigInt &rhs) {
    if (sign == rhs.sign) {
      bool carry = false;
      for (int i = 0; i < max(dat.size(), rhs.dat.size()) || carry; ++i) {
        if (i == dat.size()) dat.emplace_back(0);
        dat[i] += (i < rhs.dat.size() ? rhs.dat[i] : 0) + carry;
        carry = dat[i] >= base;
        if (carry) dat[i] -= base;
      }
    } else {
      *this -= -rhs;
    }
    return *this;
  }
  BigInt &operator-=(const BigInt &rhs) {
    if (sign == rhs.sign) {
      BigInt abs_lhs = *this, abs_rhs = rhs;
      abs_lhs.sign = 1; abs_rhs.sign = 1;
      if (abs_lhs >= abs_rhs) {
        bool carry = false;
        for (int i = 0; i < dat.size() || carry; ++i) {
          dat[i] -= (i < rhs.dat.size() ? rhs.dat[i] : 0) + carry;
          carry = dat[i] < 0;
          if (carry) dat[i] += base;
        }
        trim();
      } else {
        *this = -(rhs - *this);
      }
    } else {
      *this += -rhs;
    }
    return *this;
  }
  BigInt &operator*=(const BigInt &rhs) {
    const int new_log10_base = 6, new_base = 1000000;
    vector<long long> lhs6 = convert_base(new_log10_base, new_base), rhs6 = rhs.convert_base(new_log10_base, new_base);
    vector<long long> res = karatsuba(lhs6, 0, lhs6.size(), rhs6, 0, rhs6.size());
    REP(i, res.size()) {
      long long quo = res[i] / new_base;
      if (quo > 0) {
        if (i + 1 == res.size()) res.emplace_back(0);
        res[i + 1] += quo;
      }
      res[i] %= new_base;
    }
    string str = (sign * rhs.sign == 1 ? "+" : "-");
    for (int i = static_cast<int>(res.size()) - 1; i >= 0; --i) {
      string tmp = std::to_string(res[i]);
      REP(_, new_log10_base - tmp.size()) str += '0';
      str += tmp;
    }
    return *this = str;
  }
  BigInt &operator/=(int rhs) { return *this = divide(rhs).first; }
  BigInt &operator/=(const BigInt &rhs) { return *this = divide(rhs).first; }
  BigInt &operator%=(int rhs) { return *this = divide(rhs).second; }
  BigInt &operator%=(const BigInt &rhs) { return *this = divide(rhs).second; }
  bool operator==(const BigInt &rhs) const {
    if (sign != rhs.sign || dat.size() != rhs.dat.size()) return false;
    int sz = dat.size();
    REP(i, sz) if (dat[i] != rhs.dat[i]) return false;
    return true;
  }
  bool operator!=(const BigInt &rhs) const { return !(*this == rhs); }
  bool operator<(const BigInt &rhs) const {
    if (sign != rhs.sign) return sign < rhs.sign;
    if (dat.size() != rhs.dat.size()) return sign * dat.size() < rhs.sign * rhs.dat.size();
    for (int i = static_cast<int>(dat.size()) - 1; i >= 0; --i) {
      if (dat[i] != rhs.dat[i]) return dat[i] * sign < rhs.dat[i] * rhs.sign;
    }
    return false;
  }
  bool operator<=(const BigInt &rhs) const { return !(rhs < *this); }
  bool operator>(const BigInt &rhs) const { return rhs < *this; }
  bool operator>=(const BigInt &rhs) const { return !(*this < rhs); }
  BigInt &operator++() { return *this += 1; }
  BigInt operator++(int) { BigInt res = *this; ++*this; return res; }
  BigInt &operator--() { return *this -= 1; }
  BigInt operator--(int) { BigInt res = *this; --*this; return res; }
  BigInt operator+() const { return *this; }
  BigInt operator-() const { BigInt res = *this; res.sign *= -1; return res; }
  BigInt operator+(const BigInt &rhs) const { return BigInt(*this) += rhs; }
  BigInt operator-(const BigInt &rhs) const { return BigInt(*this) -= rhs; }
  BigInt operator*(const BigInt &rhs) const { return BigInt(*this) *= rhs; }
  BigInt operator/(int rhs) const { return BigInt(*this) /= rhs; }
  BigInt operator/(const BigInt &rhs) const { return BigInt(*this) /= rhs; }
  BigInt operator%(int rhs) const { return BigInt(*this) %= rhs; }
  BigInt operator%(const BigInt &rhs) const { return BigInt(*this) %= rhs; }
  friend ostream &operator<<(ostream &os, const BigInt &rhs) {
    if (rhs.sign == -1) os << '-';
    os << (rhs.dat.empty() ? 0 : rhs.dat.back());
    for (int i = static_cast<int>(rhs.dat.size()) - 2; i >= 0; --i) os << setw(lg10_base) << setfill('0') << rhs.dat[i];
    return os;
  }
  friend istream &operator>>(istream &is, BigInt &rhs) { string s; is >> s; rhs = s; return is; }
private:
  vector<long long> karatsuba(vector<long long> &lhs, int lhs_l, int lhs_r, vector<long long> &rhs, int rhs_l, int rhs_r) {
    int lhs_len = lhs_r - lhs_l, rhs_len = rhs_r - rhs_l;
    if (lhs_len < rhs_len) return karatsuba(rhs, rhs_l, rhs_r, lhs, lhs_l, lhs_r);
    vector<long long> res(lhs_len + rhs_len, 0);
    if (rhs_len <= 32) {
      FOR(i, lhs_l, lhs_r) FOR(j, rhs_l, rhs_r) res[(i - lhs_l) + (j - rhs_l)] += lhs[i] * rhs[j];
    } else {
      int mid = (lhs_len + 1) / 2, n = min(lhs_len, mid);
      for (int i = lhs_l; i + mid < lhs_r; ++i) lhs[i] += lhs[i + mid];
      for (int i = rhs_l; i + mid < rhs_r; ++i) rhs[i] += rhs[i + mid];
      vector<long long> tmp = karatsuba(lhs, lhs_l, lhs_l + mid, rhs, rhs_l, rhs_l + n);
      REP(i, tmp.size()) res[mid + i] = tmp[i];
      for (int i = lhs_l; i + mid < lhs_r; ++i) lhs[i] -= lhs[i + mid];
      for (int i = rhs_l; i + mid < rhs_r; ++i) rhs[i] -= rhs[i + mid];
      tmp = karatsuba(lhs, lhs_l, lhs_l + mid, rhs, rhs_l, rhs_l + n);
      REP(i, tmp.size()) { res[i] += tmp[i]; res[mid + i] -= tmp[i]; }
      tmp = karatsuba(lhs, lhs_l + mid, lhs_r, rhs, rhs_l + n, rhs_r);
      REP(i, tmp.size()) { res[mid + i] -= tmp[i]; res[(mid << 1) + i] += tmp[i]; }
    }
    while (!res.empty() && res.back() == 0) res.pop_back();
    return res;
  }
  pair<BigInt, int> divide(int rhs) {
    // assert(!rhs.dat.empty());
    BigInt dividend = *this;
    if (rhs < 0) { dividend.sign *= -1; rhs *= -1; }
    long long rem = 0;
    for (int i = static_cast<int>(dividend.dat.size()) - 1; i >= 0; --i) {
      long long tmp = rem * base + dividend.dat[i];
      dividend.dat[i] = static_cast<int>(tmp / rhs);
      rem = tmp % rhs;
    }
    dividend.trim();
    return {dividend, static_cast<int>(rem)};
  }
  pair<BigInt, BigInt> divide(const BigInt &rhs) {
    // assert(!rhs.dat.empty());
    int k = base / (rhs.dat.back() + 1);
    BigInt dividend = *this, divisor = rhs;
    dividend.sign = 1; divisor.sign = 1;
    dividend *= k; divisor *= k;
    BigInt quo, rem = 0;
    quo.dat.resize(dividend.dat.size());
    int sz = divisor.dat.size();
    for (int i = static_cast<int>(dividend.dat.size()) - 1; i >= 0; --i) {
      rem.dat.insert(rem.dat.begin(), dividend.dat[i]);
      quo.dat[i] = static_cast<int>(((sz < rem.dat.size() ? 1LL * rem.dat[sz] * base : 0) + (sz - 1 < rem.dat.size() ? rem.dat[sz - 1] : 0)) / divisor.dat.back());
      rem -= divisor * quo.dat[i];
      while (rem.sign == -1) { rem += divisor; --quo.dat[i];  }
    }
    quo.sign = sign * rhs.sign; rem.sign = sign;
    quo.trim(); rem.trim();
    return {quo, rem / k};
  }
};
BigInt __gcd(BigInt a, BigInt b) { while (!b.dat.empty()) swap(a %= b, b); return a; }
BigInt __lcm(const BigInt &a, const BigInt &b) { return a / __gcd(a, b) * b; }
BigInt abs(const BigInt &x) { BigInt res = x; res.sign = 1; return res; }
BigInt max(const BigInt &a, const BigInt &b) { return a < b ? b : a; }
BigInt min(const BigInt &a, const BigInt &b) { return a < b ? a : b; }

template <typename T, typename U>
struct PrimalDual {
  using Pui = pair<U, int>;

  struct Edge {
    int dst, rev;
    T cap;
    U cost;
    Edge(int dst, T cap, U cost, int rev) : dst(dst), cap(cap), cost(cost), rev(rev) {}
  };

  vector<vector<Edge> > graph;

  PrimalDual(int n, T TINF, U UINF) : n(n), TINF(TINF), UINF(UINF), graph(n), prev_v(n, -1), prev_e(n, -1), potential(n, 0), dist(n) {}

  void add_edge(int src, int dst, T cap, U cost) {
    graph[src].emplace_back(dst, cap, cost, graph[dst].size());
    graph[dst].emplace_back(src, 0, -cost, graph[src].size() - 1);
  }

  U minimum_cost_flow(int s, int t, T flow) {
    U res = 0;
    // bellman_ford(s);
    // if (dist[t] == UINF) return UINF;
    // res += calc(s, t, flow);
    while (flow > 0) {
      dijkstra(s);
      if (dist[t] == UINF) return UINF;
      res += calc(s, t, flow);
    }
    return res;
  }

  U minimum_cost_flow(int s, int t) {
    U res = 0;
    bellman_ford(s);
    if (potential[t] >= 0 || dist[t] == UINF) return res;
    T tmp = TINF;
    res += calc(s, t, tmp);
    while (true) {
      dijkstra(s);
      if (potential[t] >= 0 || dist[t] == UINF) return res;
      res += calc(s, t, tmp);
    }
    // TINF - tmp
  }

  pair<T, U> min_cost_max_flow(int s, int t, T flow) {
    T mx = flow;
    U cost = 0;
    bellman_ford(s);
    if (dist[t] == UINF) return {mx - flow, cost};
    cost += calc(s, t, flow);
    while (flow > 0) {
      dijkstra(s);
      if (dist[t] == UINF) return {mx - flow, cost};
      cost += calc(s, t, flow);
    }
    return {mx - flow, cost};
  }

private:
  int n;
  T TINF;
  U UINF;
  vector<int> prev_v, prev_e;
  vector<U> potential, dist;
  priority_queue<Pui, vector<Pui>, greater<Pui> > que;

  void bellman_ford(int s) {
    fill(ALL(dist), UINF);
    dist[s] = 0;
    bool is_updated = true;
    REP(step, n) {
      is_updated = false;
      REP(i, n) if (dist[i] != UINF) {
        REP(j, graph[i].size()) {
          Edge e = graph[i][j];
          if (e.cap > 0 && dist[e.dst] > dist[i] + e.cost) {
            dist[e.dst] = dist[i] + e.cost;
            prev_v[e.dst] = i;
            prev_e[e.dst] = j;
            is_updated = true;
          }
        }
      }
      if (!is_updated) break;
    }
    if (is_updated) assert(false);
    REP(i, n) {
      if (dist[i] != UINF) potential[i] += dist[i];
    }
  }

  void dijkstra(int s) {
    fill(ALL(dist), UINF);
    dist[s] = 0;
    que.emplace(0, s);
    while (!que.empty()) {
      Pui pr = que.top(); que.pop();
      int ver = pr.second;
      if (dist[ver] < pr.first) continue;
      REP(i, graph[ver].size()) {
        Edge e = graph[ver][i];
        U nx = dist[ver] + e.cost + potential[ver] - potential[e.dst];
        if (e.cap > 0 && dist[e.dst] > nx) {
          dist[e.dst] = nx;
          prev_v[e.dst] = ver;
          prev_e[e.dst] = i;
          que.emplace(dist[e.dst], e.dst);
        }
      }
    }
    REP(i, n) {
      if (dist[i] != UINF) potential[i] += dist[i];
    }
  }

  U calc(int s, int t, T &flow) {
    T f = flow;
    for (int v = t; v != s; v = prev_v[v]) f = min(f, graph[prev_v[v]][prev_e[v]].cap);
    flow -= f;
    for (int v = t; v != s; v = prev_v[v]) {
      Edge &e = graph[prev_v[v]][prev_e[v]];
      e.cap -= f;
      graph[v][e.rev].cap += f;
    }
    return potential[t] * f;
  }
};

template <typename T>
struct WeightedBipartiteMatching {
  WeightedBipartiteMatching(int left, int right, T TINF) : left(left), right(right), pd(left + right + 2, INF, TINF) {}

  void add_edge(int src, int dst, T cost) { pd.add_edge(src, left + dst, 1, -cost); }

  T solve() {
    REP(i, left) pd.add_edge(left + right, i, 1, 0);
    REP(i, right) pd.add_edge(left + i, left + right + 1, 1, 0);
    return -pd.min_cost_max_flow(left + right, left + right + 1, left).second;
  }

  vector<int> matching() {
    vector<int> res(left, -1);
    REP(i, left) {
      for (auto e : pd.graph[i]) {
        if (e.cap == 0 && left <= e.dst && e.dst < left + right) {
          res[i] = e.dst - left;
          break;
        }
      }
    }
    return res;
  }

private:
  int left, right;
  PrimalDual<int, T> pd;
};

int main() {
  int n; cin >> n;
  map<char, BigInt> cost;
  BigInt now = 0;
  for (int i = 25; i >= 0; --i) {
    cost['a' + i] = now;
    now = now * 50 + 1;
  }
  for (int i = 25; i >= 0; --i) {
    cost['A' + i] = now;
    now = now * 50 + 1;
  }
  vector<string> c(n); REP(i, n) cin >> c[i];
  WeightedBipartiteMatching<BigInt> wbm(n, n, now);
  REP(i, n) {
    cin >> c[i];
    REP(j, n) wbm.add_edge(i, j, cost[c[i][j]]);
  }
  wbm.solve();
  string ans = "";
  vector<int> matching = wbm.matching();
  REP(i, n) ans += c[i][matching[i]];
  sort(ALL(ans));
  cout << ans << '\n';
  return 0;
}
accuracy: 1
time_ms: 140
memory_kb: 3588
final_rank: 10

code_snippet: #include <vector>
#include <algorithm>
#include <iostream>
#include <utility>
#include <tuple>
#include <queue>
#include <valarray>
using namespace std;
using ll = long long int;

const int B = 52;
int get_ord(char c) {
    if('A' <= c and c <= 'Z') return c - 'A';
    return 26 + (c - 'a');
}

char get_chr(int t) {
    char chr;
    if(t < 26) chr = (char)('A' + t);
    else chr = (char)('a' + t - 26);
    return chr;
}

// Ford-Fulkerson 法による 最大流 O( F |E| )
// Bellman-Ford 法による 最小費用流 O( F |V| |E| )
// [条件に注意] Dijkstra 法による 最小費用流 O( F |E| log |V| )

template <typename CapTp=int, typename CostTp=int>
struct Edge {
    int to, rev;
    CapTp cap; CostTp cost;
    bool is_rev;
    Edge(int t, bool f, int r, CapTp ca, CostTp co=0)
        : to(t), rev(r), cap(ca), cost(co), is_rev(f) {}
};

template <typename CapTp=int, typename CostTp=int>
struct Flow {
    using Graph = vector< vector< Edge<CapTp, CostTp> > >;
    Graph G; const CapTp IA; const CostTp IO;
    vector< pair<int, int> > r_edges;
    Flow(int N_, CapTp IA_=1<<29, CostTp IO_=1<<29)
        : G(N_), IA(IA_), IO(IO_), r_edges() {}
    // 辺を追加 (from -> to に流量 ca, コスト co)
    void add_edge(int from, int to, CapTp ca, CostTp co=0) {
        G[from].emplace_back(to, false, G[to].size(), ca, co);
        G[to].emplace_back(from, true, G[from].size() - 1, 0, -co);
        r_edges.emplace_back(to, G[to].size() - 1);
    }
    // k 番目の辺にいくつ流れたか
    CapTp get_flowed_cap(size_t k) {
        if(r_edges.size() <= k) return -1;
        int v, i; tie(v, i) = r_edges[k];
        return G[v][i].cap;
    }
    // s -> t 最大流
    CapTp max_flow(int s, int t) {
        vector<bool> used(G.size());
        auto dfs = [&](auto &&func, int v, int t, CapTp f) -> CapTp {
            if(v == t) return f;
            used[v] = true;
            for(auto &e : G[v]) {
                if(used[e.to] or e.cap == 0) continue;
                CapTp d = func(func, e.to, t, min(f, e.cap));
                if(d == 0) continue;
                e.cap -= d; G[e.to][e.rev].cap += d;
                return d;
            }
            return 0;
        };

        CapTp res(0);
        while(true) {
            fill(used.begin(), used.end(), false);
            CapTp delta = dfs(dfs, s, t, IA);
            if(delta == 0) return res;
            res += delta;
        }
    }

    bool is_equal(CostTp a, CostTp b) {
        valarray<bool> x = (a == b);
        for(auto e : x) if(!e) return false;
        return true;
    }

    bool is_greateq(CostTp a, CostTp b) {
        for(size_t i=0; i<a.size(); i++) {
            if(a[i] < b[i]) return true;
            if(a[i] > b[i]) return false;
        }
        return is_equal(a, b);
    }

    // ベルマンフォードをつかって最小費用流
    CostTp mincost_flow(int s, int t, CapTp f) {
        vector<CostTp> dist(G.size()); CostTp res = valarray<int>(B);
        vector<int> prevv(G.size()), preve(G.size());
        while(f > 0) {
            fill(dist.begin(), dist.end(), IO);
            dist[s] = valarray<int>(B);
            while(1) {
                bool upd = false;
                for(int v=0; v<(int)G.size(); v++) {
                    if(is_equal(dist[v], IO)) continue;
                    for(size_t i=0; i<G[v].size(); i++) {
                        auto &e = G[v][i];
                        if(e.cap == 0 or is_greateq(dist[e.to], dist[v] + e.cost)) continue;
                        dist[e.to] = dist[v] + e.cost;
                        prevv[e.to] = v, preve[e.to] = i;
                        upd = true;
                    }
                }
                if(!upd) break;
            }

            if(is_equal(dist[t], IO)) return IO;
            
            CapTp d = f;
            for(int v=t; v!=s; v=prevv[v]) d = min(d, G[prevv[v]][preve[v]].cap);
            f -= d; res += d * dist[t];
            for(int v=t; v!=s; v=prevv[v]) {
                auto &e = G[prevv[v]][preve[v]];
                e.cap -= d, G[v][e.rev].cap += d;
            }
        }
        return res;
    }
    // ポテンシャルの導入により、ダイクストラ法で最小費用流を解く
    // [仮定している条件]
    //     1. グラフに負の閉路が存在しない (流量の 0 初期化のため)
    //        もし存在するならベルマンフォードで負の閉路を見つけ
    //        そこに流せるだけ流してスタート
    //     2. グラフに負の辺が存在しない (pot_0 の計算可能性)
    //        もし存在する場合は最初のみベルマンフォードを使う必要あり
    CostTp fast_mincost_flow(int s, int t, CapTp f) {
        CostTp res = valarray<int>(B);
        vector<CostTp> dist(G.size()), pot(G.size());
        vector<int> prevv(G.size()), preve(G.size());
        while(f > 0) {
            using PT = pair<CostTp, int>;
            priority_queue< PT, vector<PT>, greater<PT> > que;
            fill(dist.begin(), dist.end(), IO);

            dist[s] = 0;
            que.push(make_pair(0, s));
            while(!que.empty()) {
                PT cur = que.top(); que.pop();
                int v = cur.second;
                if(dist[v] < cur.first) continue;
                for(size_t i=0; i<G[v].size(); i++) {
                    auto& e = G[v][i];
                    if(e.cap > 0 and dist[e.to] > dist[v] + e.cost + pot[v] - pot[e.to]) {
                        dist[e.to] = dist[v] + e.cost + pot[v] - pot[e.to];
                        prevv[e.to] = v;
                        preve[e.to] = i;
                        que.push(make_pair(dist[e.to], e.to));
                    }
                }
            }
            if(dist[t] == IO) {
                return -1;
            }
            for(int v=0; v<(int)G.size(); v++) pot[v] += dist[v];

            CapTp d = f;
            for(int v=t; v!=s; v=prevv[v]) {
                d = min(d, G[prevv[v]][preve[v]].cap);
            }
            f -= d;
            res += d * pot[t];
            for(int v=t; v!=s; v=prevv[v]) {
                auto& e = G[prevv[v]][preve[v]];
                e.cap -= d;
                G[v][e.rev].cap += d;
            }
        }
        return res;
    }    
};

int main() {
    int N; cin >> N;

    vector< vector< pair<int, int> > > groups(B);
    vector< vector<int> > indices(B);
    for(int i=0; i<N; i++) {
        for(int j=0; j<N; j++) {
            char c; cin >> c;
            groups[get_ord(c)].emplace_back(i, j);
        }
    }

    string ans = "";
    valarray<int> MAXARRAY(60, B);
    Flow< int, valarray<int> > fl(2*N + 2, 1 << 29, MAXARRAY);
    int source = 2*N, sink = source + 1;

    int k = 0;
    for(int t=0; t<B; t++) {
        for(size_t i=0; i<groups[t].size(); i++) {
            int r, c; tie(r, c) = groups[t][i];
            indices[t].emplace_back(k++);
            valarray<int> vec(B); vec[t] = -1;
            fl.add_edge(r, N+c, 1, vec);
        }
    }

    for(int i=0; i<N; i++) {
        valarray<int> vec(B);
        fl.add_edge(source, i, 1, vec);
        fl.add_edge(N+i, sink, 1, vec);
    }

    auto mincost = fl.mincost_flow(source, sink, N);
    if(mincost[0] == 60) return 1;

    for(int t=0; t<B; t++) {
        char chr = get_chr(t);
        ans += string(-mincost[t], chr);
    }
    
    cout << ans << endl;
    return 0;
}
accuracy: 1
time_ms: 60
memory_kb: 4564
final_rank: 6

code_snippet: #include <vector>
#include <algorithm>
#include <iostream>
#include <utility>
#include <tuple>
#include <queue>
#include <valarray>
using namespace std;
using ll = long long int;

const int B = 52;
int get_ord(char c) {
    if('A' <= c and c <= 'Z') return c - 'A';
    return 26 + (c - 'a');
}

char get_chr(int t) {
    char chr;
    if(t < 26) chr = (char)('A' + t);
    else chr = (char)('a' + t - 26);
    return chr;
}

// Ford-Fulkerson 法による 最大流 O( F |E| )
// Bellman-Ford 法による 最小費用流 O( F |V| |E| )
// [条件に注意] Dijkstra 法による 最小費用流 O( F |E| log |V| )

template <typename CapTp=int, typename CostTp=int>
struct Edge {
    int to, rev;
    CapTp cap; CostTp cost;
    bool is_rev;
    Edge(int t, bool f, int r, CapTp ca, CostTp co=0)
        : to(t), rev(r), cap(ca), cost(co), is_rev(f) {}
};

template <typename CapTp=int, typename CostTp=int>
struct Flow {
    using Graph = vector< vector< Edge<CapTp, CostTp> > >;
    Graph G; const CapTp IA; const CostTp IO;
    vector< pair<int, int> > r_edges;
    Flow(int N_, CapTp IA_=1<<29, CostTp IO_=1<<29)
        : G(N_), IA(IA_), IO(IO_), r_edges() {}
    // 辺を追加 (from -> to に流量 ca, コスト co)
    void add_edge(int from, int to, CapTp ca, CostTp co=0) {
        G[from].emplace_back(to, false, G[to].size(), ca, co);
        G[to].emplace_back(from, true, G[from].size() - 1, 0, -co);
        r_edges.emplace_back(to, G[to].size() - 1);
    }
    // k 番目の辺にいくつ流れたか
    CapTp get_flowed_cap(size_t k) {
        if(r_edges.size() <= k) return -1;
        int v, i; tie(v, i) = r_edges[k];
        return G[v][i].cap;
    }
    // s -> t 最大流
    CapTp max_flow(int s, int t) {
        vector<bool> used(G.size());
        auto dfs = [&](auto &&func, int v, int t, CapTp f) -> CapTp {
            if(v == t) return f;
            used[v] = true;
            for(auto &e : G[v]) {
                if(used[e.to] or e.cap == 0) continue;
                CapTp d = func(func, e.to, t, min(f, e.cap));
                if(d == 0) continue;
                e.cap -= d; G[e.to][e.rev].cap += d;
                return d;
            }
            return 0;
        };

        CapTp res(0);
        while(true) {
            fill(used.begin(), used.end(), false);
            CapTp delta = dfs(dfs, s, t, IA);
            if(delta == 0) return res;
            res += delta;
        }
    }

    bool is_equal(CostTp a, CostTp b) {
        valarray<bool> x = (a == b);
        for(auto e : x) if(!e) return false;
        return true;
    }

    bool is_greateq(CostTp a, CostTp b) {
        /*
        cerr << "# a: "; for(auto e : a) cerr << e << " "; cerr << endl;
        cerr << "# b: "; for(auto e : b) cerr << e << " "; cerr << endl;
        */

        // bool res = false;
        for(size_t i=0; i<a.size(); i++) {
            if(a[i] < b[i]) return true;
            if(a[i] > b[i]) return false;
        }
        return is_equal(a, b);
        // res |= is_equal(a, b);

        // cerr << "# res: " << (res ? "true" : "false") << endl << endl;
        // return res;
    }

    // ベルマンフォードをつかって最小費用流
    CostTp mincost_flow(int s, int t, CapTp f) {
        vector<CostTp> dist(G.size()); CostTp res = valarray<int>(B);
        vector<int> prevv(G.size()), preve(G.size());
        while(f > 0) {
            fill(dist.begin(), dist.end(), IO);
            dist[s] = valarray<int>(B);
            while(1) {
                bool upd = false;
                for(int v=0; v<(int)G.size(); v++) {
                    if(is_equal(dist[v], IO)) continue;
                    for(size_t i=0; i<G[v].size(); i++) {
                        auto &e = G[v][i];
                        if(e.cap == 0 or is_greateq(dist[e.to], dist[v] + e.cost)) continue;
                        dist[e.to] = dist[v] + e.cost;
                        prevv[e.to] = v, preve[e.to] = i;
                        upd = true;
                    }
                }

                /*
                cerr << "?";
                for(auto xx : dist[t]) cerr << " " << xx;
                cerr << endl;
                */                

                if(!upd) break;
            }

            if(is_equal(dist[t], IO)) return IO;
            
            CapTp d = f;
            for(int v=t; v!=s; v=prevv[v]) d = min(d, G[prevv[v]][preve[v]].cap);
            f -= d; res += d * dist[t];
            for(int v=t; v!=s; v=prevv[v]) {
                auto &e = G[prevv[v]][preve[v]];
                e.cap -= d, G[v][e.rev].cap += d;
            }
        }
        return res;
    }
    // ポテンシャルの導入により、ダイクストラ法で最小費用流を解く
    // [仮定している条件]
    //     1. グラフに負の閉路が存在しない (流量の 0 初期化のため)
    //        もし存在するならベルマンフォードで負の閉路を見つけ
    //        そこに流せるだけ流してスタート
    //     2. グラフに負の辺が存在しない (pot_0 の計算可能性)
    //        もし存在する場合は最初のみベルマンフォードを使う必要あり
    CostTp fast_mincost_flow(int s, int t, CapTp f) {
        CostTp res = valarray<int>(B);
        vector<CostTp> dist(G.size()), pot(G.size());
        vector<int> prevv(G.size()), preve(G.size());
        while(f > 0) {
            using PT = pair<CostTp, int>;
            priority_queue< PT, vector<PT>, greater<PT> > que;
            fill(dist.begin(), dist.end(), IO);

            dist[s] = 0;
            que.push(make_pair(0, s));
            while(!que.empty()) {
                PT cur = que.top(); que.pop();
                int v = cur.second;
                if(dist[v] < cur.first) continue;
                for(size_t i=0; i<G[v].size(); i++) {
                    auto& e = G[v][i];
                    if(e.cap > 0 and dist[e.to] > dist[v] + e.cost + pot[v] - pot[e.to]) {
                        dist[e.to] = dist[v] + e.cost + pot[v] - pot[e.to];
                        prevv[e.to] = v;
                        preve[e.to] = i;
                        que.push(make_pair(dist[e.to], e.to));
                    }
                }
            }
            if(dist[t] == IO) {
                return -1;
            }
            for(int v=0; v<(int)G.size(); v++) pot[v] += dist[v];

            CapTp d = f;
            for(int v=t; v!=s; v=prevv[v]) {
                d = min(d, G[prevv[v]][preve[v]].cap);
            }
            f -= d;
            res += d * pot[t];
            for(int v=t; v!=s; v=prevv[v]) {
                auto& e = G[prevv[v]][preve[v]];
                e.cap -= d;
                G[v][e.rev].cap += d;
            }
        }
        return res;
    }    
};

int main() {
    int N; cin >> N;

    vector< vector< pair<int, int> > > groups(B);
    vector< vector<int> > indices(B);
    for(int i=0; i<N; i++) {
        for(int j=0; j<N; j++) {
            char c; cin >> c;
            groups[get_ord(c)].emplace_back(i, j);
        }
    }

    string ans = "";
    valarray<int> MAXARRAY(60, B);
    Flow< int, valarray<int> > fl(2*N + 2, 1 << 29, MAXARRAY);
    int source = 2*N, sink = source + 1;

    int k = 0;
    for(int t=0; t<B; t++) {
        for(size_t i=0; i<groups[t].size(); i++) {
            int r, c; tie(r, c) = groups[t][i];
            indices[t].emplace_back(k++);
            valarray<int> vec(B); vec[t] = -1;
            fl.add_edge(r, N+c, 1, vec);
        }
    }

    for(int i=0; i<N; i++) {
        valarray<int> vec(B);
        fl.add_edge(source, i, 1, vec);
        fl.add_edge(N+i, sink, 1, vec);
    }

    auto mincost = fl.mincost_flow(source, sink, N);
    // cerr << "# " << mincost.size() << endl;
    if(mincost[0] == 60) return 1;

    /*
    for(int t=0; t<B; t++) {
        cout << mincost[t] << endl;
    }
    */
    for(int t=0; t<B; t++) {
        char chr = get_chr(t);
        ans += string(-mincost[t], chr);
    }
    
    cout << ans << endl;
    return 0;
}
accuracy: 1
time_ms: 60
memory_kb: 4556
final_rank: 5

code_snippet: #include <vector>
#include <algorithm>
#include <iostream>
#include <utility>
#include <tuple>
#include <queue>
#include <valarray>
using namespace std;
using ll = long long int;

const int B = 52;
int get_ord(char c) {
    if('A' <= c and c <= 'Z') return c - 'A';
    return 26 + (c - 'a');
}

char get_chr(int t) {
    char chr;
    if(t < 26) chr = (char)('A' + t);
    else chr = (char)('a' + t - 26);
    return chr;
}

// Ford-Fulkerson 法による 最大流 O( F |E| )
// Bellman-Ford 法による 最小費用流 O( F |V| |E| )
// [条件に注意] Dijkstra 法による 最小費用流 O( F |E| log |V| )

template <typename CapTp=int, typename CostTp=int>
struct Edge {
    int to, rev;
    CapTp cap; CostTp cost;
    bool is_rev;
    Edge(int t, bool f, int r, CapTp ca, CostTp co=0)
        : to(t), rev(r), cap(ca), cost(co), is_rev(f) {}
};

template <typename CapTp=int, typename CostTp=int>
struct Flow {
    using Graph = vector< vector< Edge<CapTp, CostTp> > >;
    Graph G; const CapTp IA; const CostTp IO;
    vector< pair<int, int> > r_edges;
    Flow(int N_, CapTp IA_=1<<29, CostTp IO_=1<<29)
        : G(N_), IA(IA_), IO(IO_), r_edges() {}
    // 辺を追加 (from -> to に流量 ca, コスト co)
    void add_edge(int from, int to, CapTp ca, CostTp co=0) {
        G[from].emplace_back(to, false, G[to].size(), ca, co);
        G[to].emplace_back(from, true, G[from].size() - 1, 0, -co);
        r_edges.emplace_back(to, G[to].size() - 1);
    }
    // k 番目の辺にいくつ流れたか
    CapTp get_flowed_cap(size_t k) {
        if(r_edges.size() <= k) return -1;
        int v, i; tie(v, i) = r_edges[k];
        return G[v][i].cap;
    }
    // s -> t 最大流
    CapTp max_flow(int s, int t) {
        vector<bool> used(G.size());
        auto dfs = [&](auto &&func, int v, int t, CapTp f) -> CapTp {
            if(v == t) return f;
            used[v] = true;
            for(auto &e : G[v]) {
                if(used[e.to] or e.cap == 0) continue;
                CapTp d = func(func, e.to, t, min(f, e.cap));
                if(d == 0) continue;
                e.cap -= d; G[e.to][e.rev].cap += d;
                return d;
            }
            return 0;
        };

        CapTp res(0);
        while(true) {
            fill(used.begin(), used.end(), false);
            CapTp delta = dfs(dfs, s, t, IA);
            if(delta == 0) return res;
            res += delta;
        }
    }

    bool is_equal(CostTp a, CostTp b) {
        valarray<bool> x = (a == b);
        for(auto e : x) if(!e) return false;
        return true;
    }

    bool is_greateq(CostTp a, CostTp b) {
        string s = "", t = "";
        for(size_t i=0; i<a.size(); i++) {
            if(b[i] < 0) return true;
            s += string(a[i], get_chr(i));
            t += string(b[i], get_chr(i));
        }
        return s <= t;
    }

    // ベルマンフォードをつかって最小費用流
    CostTp mincost_flow(int s, int t, CapTp f) {
        vector<CostTp> dist(G.size()); CostTp res = valarray<int>(B);
        vector<int> prevv(G.size()), preve(G.size());
        while(f > 0) {
            fill(dist.begin(), dist.end(), IO);
            dist[s] = valarray<int>(B);
            while(1) {
                bool upd = false;
                for(int v=0; v<(int)G.size(); v++) {
                    if(is_equal(dist[v], IO)) continue;
                    for(size_t i=0; i<G[v].size(); i++) {
                        auto &e = G[v][i];
                        if(e.cap == 0 or is_greateq(dist[e.to], dist[v] + e.cost)) continue;
                        dist[e.to] = dist[v] + e.cost;
                        prevv[e.to] = v, preve[e.to] = i;
                        upd = true;
                    }
                }
                
                if(!upd) break;
            }

            if(is_equal(dist[t], IO)) return CostTp(0);
            
            CapTp d = f;
            for(int v=t; v!=s; v=prevv[v]) d = min(d, G[prevv[v]][preve[v]].cap);
            f -= d; res += d * dist[t];
            for(int v=t; v!=s; v=prevv[v]) {
                auto &e = G[prevv[v]][preve[v]];
                e.cap -= d, G[v][e.rev].cap += d;
            }
        }
        return res;
    }
    // ポテンシャルの導入により、ダイクストラ法で最小費用流を解く
    // [仮定している条件]
    //     1. グラフに負の閉路が存在しない (流量の 0 初期化のため)
    //        もし存在するならベルマンフォードで負の閉路を見つけ
    //        そこに流せるだけ流してスタート
    //     2. グラフに負の辺が存在しない (pot_0 の計算可能性)
    //        もし存在する場合は最初のみベルマンフォードを使う必要あり
    CostTp fast_mincost_flow(int s, int t, CapTp f) {
        CostTp res = valarray<int>(B);
        vector<CostTp> dist(G.size()), pot(G.size());
        vector<int> prevv(G.size()), preve(G.size());
        while(f > 0) {
            using PT = pair<CostTp, int>;
            priority_queue< PT, vector<PT>, greater<PT> > que;
            fill(dist.begin(), dist.end(), IO);

            dist[s] = 0;
            que.push(make_pair(0, s));
            while(!que.empty()) {
                PT cur = que.top(); que.pop();
                int v = cur.second;
                if(dist[v] < cur.first) continue;
                for(size_t i=0; i<G[v].size(); i++) {
                    auto& e = G[v][i];
                    if(e.cap > 0 and dist[e.to] > dist[v] + e.cost + pot[v] - pot[e.to]) {
                        dist[e.to] = dist[v] + e.cost + pot[v] - pot[e.to];
                        prevv[e.to] = v;
                        preve[e.to] = i;
                        que.push(make_pair(dist[e.to], e.to));
                    }
                }
            }
            if(dist[t] == IO) {
                return -1;
            }
            for(int v=0; v<(int)G.size(); v++) pot[v] += dist[v];

            CapTp d = f;
            for(int v=t; v!=s; v=prevv[v]) {
                d = min(d, G[prevv[v]][preve[v]].cap);
            }
            f -= d;
            res += d * pot[t];
            for(int v=t; v!=s; v=prevv[v]) {
                auto& e = G[prevv[v]][preve[v]];
                e.cap -= d;
                G[v][e.rev].cap += d;
            }
        }
        return res;
    }    
};

int main() {
    int N; cin >> N;

    vector< vector< pair<int, int> > > groups(B);
    vector< vector<int> > indices(B);
    for(int i=0; i<N; i++) {
        for(int j=0; j<N; j++) {
            char c; cin >> c;
            groups[get_ord(c)].emplace_back(i, j);
        }
    }

    string ans = "";
    valarray<int> MAXARRAY(B); MAXARRAY[B-1] = N;
    Flow< int, valarray<int> > fl(2*N + 2, 1 << 29, MAXARRAY);
    int source = 2*N, sink = source + 1;

    int k = 0;
    for(int t=0; t<B; t++) {
        for(size_t i=0; i<groups[t].size(); i++) {
            int r, c; tie(r, c) = groups[t][i];
            indices[t].emplace_back(k++);
            valarray<int> vec(B); vec[t] = 1;
            fl.add_edge(r, N+c, 1, vec);
        }
    }

    for(int i=0; i<N; i++) {
        valarray<int> vec(B);
        fl.add_edge(source, i, 1, vec);
        fl.add_edge(N+i, sink, 1, vec);
    }

    auto mincost = fl.mincost_flow(source, sink, N);
    // cerr << "# " << mincost.size() << endl;
    if(mincost.size() == 0) return 1;

    /*
    for(int t=0; t<B; t++) {
        cout << mincost[t] << endl;
    }
    */
    for(int t=0; t<B; t++) {
        char chr = get_chr(t);
        ans += string(mincost[t], chr);
    }
    
    cout << ans << endl;
    return 0;
}
accuracy: 0.23255813953488372
time_ms: 110
memory_kb: 3712
final_rank: 16