|
|
#include <bits/stdc++.h>
|
|
|
|
|
|
using namespace std;
|
|
|
|
|
|
const int M = 1010; //字符串数量上限,可以理解为边数
|
|
|
const int N = 26; //a-z映射了26个数字,也就是图中结点的数字上限
|
|
|
|
|
|
vector<string> str; //输入的字符串数组
|
|
|
vector<int> path; //结点的搜索路径
|
|
|
int n; //字符串数量
|
|
|
bool st[M]; //是否使用
|
|
|
int fa[N]; //并查集数组
|
|
|
int in[N]; //入度
|
|
|
int out[N]; //出度
|
|
|
unordered_map<int, int> _map;//用来记录某个结点号是否出现过
|
|
|
|
|
|
|
|
|
//并查集,用来判断底图的连通性
|
|
|
int find(int x) {
|
|
|
if (x != fa[x]) fa[x] = find(fa[x]);
|
|
|
return fa[x];
|
|
|
}
|
|
|
|
|
|
//加入家族集合中
|
|
|
void join(int a, int b) {
|
|
|
int f1 = find(a), f2 = find(b);
|
|
|
if (f1 != f2)fa[f1] = f2;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 功能:深度优先搜索
|
|
|
* @param u 从哪个结点ID出发
|
|
|
* @param step 已经走了多少条边
|
|
|
*/
|
|
|
void dfs(int u, int step) {
|
|
|
//如果成功走了n条边,则是成功的标识
|
|
|
if (step == n) {
|
|
|
//输出路径
|
|
|
for (int i = 0; i < path.size(); i++) {
|
|
|
cout << str[path[i]];
|
|
|
if (i < path.size() - 1) cout << '.'; //最后一个不输出.,只有前面n-1个输出.
|
|
|
}
|
|
|
exit(0);
|
|
|
}
|
|
|
//遍历所有可能性,走多叉树,找出欧拉路径,(1)找到就直接退出,(2)从start开始肯定有,这两条保证了效率的提升
|
|
|
for (int i = 0; i < n; i++) {
|
|
|
//找到能和u结点匹配的下一个未使用过的字符串
|
|
|
if (!st[i] && str[i][0] - 'a' == u) {
|
|
|
path.push_back(i); //放入到路径中
|
|
|
st[i] = true; //标识为已使用
|
|
|
//开始尝试下一个字符串
|
|
|
dfs(str[i].back() - 'a', step + 1);
|
|
|
//回溯
|
|
|
st[i] = false; //未使用
|
|
|
path.pop_back(); //路径弹出
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
int main() {
|
|
|
//输入字符串
|
|
|
cin >> n;
|
|
|
string s;
|
|
|
for (int i = 0; i < n; i++) cin >> s, str.push_back(s);
|
|
|
|
|
|
// 排序确保搜索字典序最小
|
|
|
sort(str.begin(), str.end());
|
|
|
|
|
|
//并查集初始化,每个人都是自己的祖先
|
|
|
for (int i = 0; i < N; i++) fa[i] = i;
|
|
|
|
|
|
//检查一下是不是底图连通,采用并查集,通俗点说:就是看看是不是能全部连通在一起,如果不能,最后是多个家族,则不是欧拉图
|
|
|
for (int i = 0; i < n; i++) {
|
|
|
//每行是一个由 1 到 20 个"小写"字母组成的单词
|
|
|
int a = str[i][0] - 'a'; //首字母映射号,相当于结点编号
|
|
|
int b = str[i].back() - 'a'; //尾字母映射号,相当于结点编号
|
|
|
//转为数字,建立并查集之间的关系,这个字符映射数字用的很妙。
|
|
|
|
|
|
// join合并并查集
|
|
|
join(a, b);
|
|
|
//标识a和b都使用过
|
|
|
_map[a]++, _map[b]++;
|
|
|
//记录b的入度++
|
|
|
in[b]++;
|
|
|
//记录a的出度++
|
|
|
out[a]++;
|
|
|
//之所以记录出度和入度,是为了下一步的欧拉图二次检测
|
|
|
}
|
|
|
// 判断欧拉图的第一个要求:底图连通性
|
|
|
int cnt = 0; //家族的数量
|
|
|
for (int i = 0; i < N; i++) if (fa[i] == i && _map[i]) cnt++;//自己是自己的祖先,并且出现过
|
|
|
|
|
|
//并查集的家族个数大于1,表示不可能是欧拉图
|
|
|
if (cnt > 1) {
|
|
|
cout << "***";
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
int flag = 0; //出度与入度差是1的个数
|
|
|
int start = str[0][0] - 'a';//默认是第一个字符串的第一个字符对应的结点
|
|
|
// 判断欧拉图的第二个要求:出度与入度的数字关系
|
|
|
for (int i = 0; i < N; i++) {
|
|
|
//计算每个结点的出度与入度的差
|
|
|
int k = out[i] - in[i];
|
|
|
//出度与入度差大于1,则肯定不是欧拉图
|
|
|
if (abs(k) > 1) {
|
|
|
cout << "***";
|
|
|
return 0;
|
|
|
}
|
|
|
//如果差是1,那么需要检查是不是2个,2个才是一个入口点,一个出口点
|
|
|
if (abs(k) == 1) {
|
|
|
//记录个数
|
|
|
flag++;
|
|
|
//如果出度比入度大1,记录下起点是哪个结点
|
|
|
if (k == 1) start = i;
|
|
|
}
|
|
|
}
|
|
|
//如果不是0也不是2,那么不是欧拉图
|
|
|
if (flag != 0 && flag != 2) {
|
|
|
cout << "***";
|
|
|
return 0;
|
|
|
}
|
|
|
//这时,肯定是有一条欧拉路径的了,找出这条欧拉路径
|
|
|
dfs(start, 0);
|
|
|
return 0;
|
|
|
} |