原题链接:https://www.acwing.com/problem/content/1187/
/*
26个字母之间建立边,单词作为边
只需要判断两点:
图是否联通,这个可以使用并查集,只要是被这个单词连过就加到把首位
字母加到一个集合中
除了起点和终点以外,其他点的入度等于出度。
首先需要找到起点和终点。起点的出度=入度+1,并且仅有一个起点
终点的入度=出度+1,并且只有一个。
其他点的入度等于出度。
以上两个条件任意一个不满足,都不成立。
*/
#include <iostream>
#include <cstring>
using namespace std;
const int N = 30; // 点数最多26个
int n; // 单词数目,图中对应边数
int p[N]; // 并查集
int din[N], dout[N];
bool st[N]; // 记录某个字母是否作为单词首尾出现过
int find(int x) {
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
int main() {
char str[1010];
int T;
scanf("%d", &T);
while (T--) {
cin >> n;
memset(din, 0, sizeof din);
memset(dout, 0, sizeof dout);
memset(st, 0, sizeof st);
for (int i = 0; i < 26; i++) p[i] = i;
for (int i = 0; i < n; i++) {
scanf("%s", str);
int a = str[0] - 'a', b = str[strlen(str) - 1] - 'a';
st[a] = st[b] = true;
// a的出度加一。b的入度加一
dout[a]++, din[b]++;
// 合并
p[find(a)] = find(b);
}
// 判断度数是否正确
int start = 0, end = 0;
bool success = true; // 是否存在欧拉路径
for (int i = 0; i < 26; i++)
if (din[i] != dout[i]) {
if (din[i] == dout[i] + 1) end++; // 终点数目,只能有1个
else if (dout[i] == din[i] + 1) start++; // 起点数目,只能有一个
else {
success = false;
break;
}
}
// 判断起点终点数目是否正确
if (success && !(!start && !end || start == 1 && end == 1)) success = false;
// 判断所有边是否连通
int rep = -1; // 代表元素
for (int i = 0; i < 26; i++)
if (st[i]) { // 说明该字母在单词中出现过,所有st为true的都应该在一个集合中
if (rep == -1) rep = find(i);
else if (rep != find(i)) { // 说明存在边不连通
success = false;
break;
}
}
if (success) puts("Ordering is possible.");
else puts("The door cannot be opened.");
}
return 0;
}