/*
http://acm.pku.edu.cn/JudgeOnline/problem?id=1128
DFS回溯实现拓扑排序
*/
#include <iostream>
#include <memory>
#include <string>
#include <algorithm>
#define MAX_N 32
#define MAX_L 26
using namespace std;
char mapv[MAX_N + 1][MAX_N + 1]; //记录输入
char letter[MAX_L + 1]; //记录输入中输出的字母c, C - 'A' + 1
bool graph[MAX_L + 1][MAX_L + 1]; //graph[i][j] = true, 表示i frame 排在 j frame的上面
int rowN, colN, lNum, resNum; //输入的行数,列数,lnum表述输入中出现的字母的类别数,resNum记录输出的结果数目
int pos[MAX_L + 1][4]; //记录各个字母上下左右的Index, 从1开始记录
bool v[MAX_L + 1];
//记录结果
struct node
{
string seq;
node()
{
seq = "";
}
}res[200]; //从0开始存
void init()
{
resNum = lNum = 0;
memset(pos, 0, sizeof(pos));
memset(graph, 0, sizeof(graph));
}
bool compare(const node n1, const node n2)
{
if(n1.seq <= n2.seq)
return true;
return false;
}
void topSort(int layer, string seqStr)
{
//排完了,每排一个字母,layer + 1, 直到排完所有的字母
if(layer > lNum)
{
//记录结果
res[resNum++].seq = seqStr;
return;
}
int minCom = INT_MAX, s, k, curL, curCom;
int minNum = 0;
//向temp这类数据必须使用局部数据,应为没一层的temp必须不能受其他层影响,
//一开把temp设成了全局的,这样进入下一层后,下一层可能会修改上面层的temp,从而产生错误结果
int temp[MAX_L + 1];
//寻找入度最小的点,这个点对应的frame应该是放在最上面的frame(可能不唯一,所以会产生多个结果)
for(s = 0; s < lNum; s++)
{
//当前结点是上面已经处理过已经被抹去的点
if(letter[s] == '.')
continue;
curCom = 0;
curL = letter[s] - 'A' + 1;
for(k = 1; k <= MAX_L; k++)
if(graph[k][curL])
curCom++;
//找到更小入度的点
if(curCom < minCom)
{
minCom = curCom;
minNum = 1;
temp[minNum] = curL; //temp 从1开始存
}
//和最小度数相等,则记录,并使minNum + 1
else if(curCom == minCom)
{
temp[++minNum] = curL;
}
}
//回溯遍历所有的当前入度最小的点
seqStr = "." + seqStr;
for(s = 1; s <= minNum; s++)
{
curL = temp[s];
//copyR和copyC也必须是局部数,原因和temp相同
bool copyR[MAX_L + 1], copyC[MAX_L + 1];
memset(copyR, 0, sizeof(copyR));
memset(copyC, 0, sizeof(copyC));
//保存当前节点对应的数据,并置位
for(k = 1; k <= MAX_L; k++)
{
copyR[k] = graph[curL][k];
copyC[k] = graph[k][curL];
graph[curL][k] = graph[k][curL] = false;
}
seqStr[0] = curL - 1 + 'A';
//已经处理过的节点要摸去
int tempPos;
for(k = 0; k < lNum; k++)
{
if(letter[k] == curL - 1 + 'A')
{
tempPos = k;
letter[k] = '.';
break;
}
}
//dfs
topSort(layer + 1, seqStr);
//恢复相关数据
letter[tempPos] = curL - 1 + 'A';
for(k = 1; k <= MAX_L; k++)
{
graph[curL][k] = copyR[k];
graph[k][curL] = copyC[k];
}
}
}
int main()
{
int i, j, k;
char c, cc;
while(cin>>rowN)
{
cin>>colN;
//input process
init();
for(i = 1; i <= rowN; i++)
{
for(j = 1; j <= colN; j++)
{
cin>>c;
mapv[i][j] = c;
if(c >= 'A' && c <= 'Z')
{
for(k = 0; k < lNum; k++)
if(letter[k] == c)
break;
if(k >= lNum)
letter[lNum++] = c;
}
}
}
///////////寻找每个frame最上方和最下方的行号,已经最左边和最右边的列号
int s, p0, p1, p2, t, curL;
//扫描行
for(i = 1; i <= rowN; i++)
{
//扫描任意列
for(j = 1; j <= colN; j++)
{
c = mapv[i][j];
cc = mapv[rowN - i + 1][j];
if(c >= 'A' && c <= 'Z')
{
if(pos[c - 'A' + 1][0] == 0)
pos[c - 'A' + 1][0] = i;
}
if(cc >= 'A' && cc <= 'Z')
{
if(pos[cc - 'A' + 1][1] == 0)
pos[cc - 'A' + 1][1] = rowN - i + 1;
}
}
}
//列扫描
for(i = 1; i <= colN; i++)
{
//扫描任意行
for(j = 1; j <= rowN; j++)
{
c = mapv[j][i];
cc = mapv[j][colN - i + 1];
if(c >= 'A' && c <= 'Z')
{
if(pos[c - 'A' + 1][2] == 0)
pos[c - 'A' + 1][2] = i;
}
if(cc >= 'A' && cc <= 'Z')
{
if(pos[cc - 'A' + 1][3] == 0)
pos[cc - 'A' + 1][3] = colN - i + 1;
}
}
}
///////////顺次处理每个节点s,如果在s的四条边线中夹杂有其他节点t,那么表示
//t遮住了s, graph[t][s] 设置为true
for(s = 0; s < lNum; s++)
{
curL = letter[s] - 'A' + 1;
memset(v, 0, sizeof(v));
for(i = 0; i < 4; i++)
{
p0 = pos[curL][i];
int j = ((int(i / 2) + 1) * 2) % 4;
p1 = pos[curL][j];
p2 = pos[curL][j + 1];
for(t = p1; t <= p2; t ++)
{
if(i <= 1)
{
if(mapv[p0][t] == letter[s])
continue;
if(v[mapv[p0][t] - 'A' + 1])
continue;
if(!(mapv[p0][t] >= 'A' && mapv[p0][t] <= 'Z'))
continue;
v[mapv[p0][t] - 'A' + 1] = true;
graph[mapv[p0][t] - 'A' + 1][curL] = true;
}
else
{
if(mapv[t][p0] == letter[s])
continue;
if(v[mapv[t][p0] - 'A' + 1])
continue;
if(!(mapv[t][p0] >= 'A' && mapv[t][p0] <= 'Z'))
continue;
v[mapv[t][p0] - 'A' + 1] = true;
graph[mapv[t][p0] - 'A' + 1][curL] = true;
}
}
}
}
string str = "";
topSort(1, str);
sort(res, res + resNum, compare);
for(i = 0; i < resNum; i++)
cout<<res[i].seq<<endl;
/////////////////
}
return 0;
}