题目地址:
https://www.acwing.com/problem/content/1187/
有 N N N个盘子,每个盘子上写着一个仅由小写字母组成的英文单词。你需要给这些盘子安排一个合适的顺序,使得相邻两个盘子中,前一个盘子上单词的末字母等于后一个盘子上单词的首字母。请你编写一个程序,判断是否能达到这一要求。
输入格式:
第一行包含整数 T T T,表示共有 T T T组测试数据。每组数据第一行包含整数 N N N,表示盘子数量。接下来 N N N行,每行包含一个小写字母字符串,表示一个盘子上的单词。一个单词可能出现多次。
输出格式:
如果存在合法解,则输出”Ordering is possible.”
,否则输出”The door cannot be opened.”
。
数据范围:
1 ≤ N ≤ 1 0 5 1≤N≤10^5 1≤N≤105
单词长度均不超过 1000 1000 1000
如果把每个字母看成是图上的一个点,盘子看成是边,那么题目相当于在问一个有向图是否存在欧拉路径。一个有向图存在欧拉路径的充要条件是图是连通的(本题不会存在孤立点,所以考虑点连通即可),并且要么所有点的入度都等于出度(则存在欧拉回路),要么存在两个特殊的点,除了这两个点之外所有点入度都等于出度,而这两个点一个是起点一个是终点,起点是出度比入度多 1 1 1,终点是入度比出度多 1 1 1(则存在非回路的欧拉路径)。连通性可以用并查集判断。代码如下:
#include <iostream>
#include <cstring>
using namespace std;
const int N = 30;
int n;
// p数组是并查集里的parent数组
int din[N], dout[N], p[N];
// st[i]记录字母i是否出现过
bool st[N];
// 返回并查集里的i所在树的树根
int find(int x) {
if (x != p[x]) p[x] = find(p[x]);
return p[x];
}
void merge(int x, int y) {
int px = find(x), py = find(y);
if (px == py) return;
p[px] = py;
}
int main() {
string s;
int T;
cin >> T;
while (T--) {
// 重置一下各个变量
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;
cin >> n;
for (int i = 0; i < n; i++) {
cin >> s;
// 说明从a到b有条边,则a的出度加1,b的入度加1
int a = s[0] - 'a', b = s[s.size() - 1] - 'a';
st[a] = st[b] = true;
dout[a]++, din[b]++;
// 做合并操作
merge(a, b);
}
int start = 0, end = 0;
// res存是否存在欧拉路径
bool res = true;
// 看一下能充当起点和终点的顶点有多少个,并且如果某个点的入度与出度个数差大于了1,则肯定不存在欧拉路
for (int i = 0; i < 26; i++) {
if (din[i] != dout[i]) {
if (din[i] == dout[i] + 1) end++;
else if (din[i] + 1 == dout[i]) start++;
else {
res = false;
break;
}
}
}
// 如果每个顶点的度的情况都是合法的,但是起点和终点的情况非法,那么也不存在欧拉路径
if (res && !(start == 0 && end == 0 || start == 1 && end == 1)) res = false;
// 看一下是否所有顶点都是连通的
int parent = -1;
for (int i = 0; i < 26; i++)
if (st[i]) {
if (parent == -1) parent = find(i);
else if (parent != find(i)) {
res = false;
break;
}
}
cout << (res ? "Ordering is possible." : "The door cannot be opened.") << endl;
}
return 0;
}
时空复杂度 O ( N ) O(N) O(N)。