题解:AcWing 1185 单词游戏
【题目来源】
AcWing:1185. 单词游戏 - AcWing题库
【题目描述】
有 \(N\) 个盘子,每个盘子上写着一个仅由小写字母组成的英文单词。
你需要给这些盘子安排一个合适的顺序,使得相邻两个盘子中,前一个盘子上单词的末字母等于后一个盘子上单词的首字母。
请你编写一个程序,判断是否能达到这一要求。
【输入】
第一行包含整数 \(T\),表示共有 \(T\) 组测试数据。
每组数据第一行包含整数 \(N\),表示盘子数量。
接下来 \(N\) 行,每行包含一个小写字母字符串,表示一个盘子上的单词。
一个单词可能出现多次。
【输出】
如果存在合法解,则输出”Ordering is possible.”,否则输出”The door cannot be opened.”。
【输入样例】
3
2
acm
ibm
3
acm
malform
mouse
2
ok
ok
【输出样例】
The door cannot be opened.
Ordering is possible.
The door cannot be opened.
【核心思想】
-
问题分析:给定 \(N\) 个单词,需要安排顺序使得相邻单词中前一个的末字母等于后一个的首字母。将每个单词看作一条从首字母指向尾字母的有向边,问题转化为判断该有向图中是否存在欧拉路径(经过每条边恰好一次的路径)。这是一个欧拉路径判定问题,关键在于检查图的连通性和度数条件。
-
算法选择:
- 并查集:检查所有出现过的字母是否在同一个连通分量中
- 入度/出度统计:统计每个字母作为单词开头(出度)和结尾(入度)的次数
- 欧拉路径判定条件:连通且满足特定的度数条件
-
关键步骤:
- 建图:读取每个单词,首字母为 \(a\),尾字母为 \(b\)
dout[a]++:首字母出度加1din[b]++:尾字母入度加1p[find(a)] = find(b):并查集合并首尾字母
- 检查度数条件:
- 遍历26个字母,统计入度不等于出度的节点
din[i] == dout[i] + 1:入度比出度多1,作为终点din[i] + 1 == dout[i]:出度比入度多1,作为起点- 其他情况:度数差超过1,不可能存在欧拉路径
- 合法条件:起点和终点都为0(欧拉回路),或起点和终点都为1(欧拉路径)
- 检查连通性:
- 遍历所有出现过的字母,检查是否属于同一个并查集集合
- 输出结果:若度数条件和连通性都满足,则存在合法解
- 建图:读取每个单词,首字母为 \(a\),尾字母为 \(b\)
-
时间/空间复杂度:
- 时间复杂度:\(O(T \times (N \alpha(26) + 26))\),其中 \(T\) 为测试组数,\(\alpha\) 为阿克曼函数的反函数,可视为常数
- 空间复杂度:\(O(26)\),只需维护26个字母的度数和并查集
-
欧拉路径的核心思想:
- 问题转化:将单词接龙问题转化为有向图的欧拉路径问题,每个单词是一条有向边
- 度数守恒:欧拉回路要求所有点入度等于出度;欧拉路径要求恰好一个起点(出度比入度多1)和一个终点(入度比出度多1)
- 连通性检查:所有有边的节点必须在同一个连通分量中,否则无法遍历所有边
- 并查集应用:高效判断连通性,支持动态合并和查询
- 适用于单词接龙、一笔画问题、路径覆盖类问题
【算法标签】
欧拉路径
【代码详解】
#include <bits/stdc++.h>
using namespace std;
// ================= 常量与全局变量 =================
const int N = 100005; // 最大单词数量
int T; // 测试数据组数
int n; // 每组数据的单词数量
int din[N]; // 每个字母的入度(作为末尾字母出现的次数)
int dout[N]; // 每个字母的出度(作为开头字母出现的次数)
int p[N]; // 并查集数组
bool st[N]; // 标记字母是否出现过
char str[N]; // 存储当前单词
// ================= 并查集:查找根节点 =================
int find(int x)
{
if (p[x] != x)
p[x] = find(p[x]);
return p[x];
}
// ================= 主函数 =================
int main()
{
cin >> 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;
// 读入 n 个单词,统计出入度和连通性
for (int i = 0; i < n; i++)
{
cin >> str;
int len = strlen(str);
int a = str[0] - 'a'; // 首字母
int b = str[len - 1] - 'a'; // 尾字母
st[a] = st[b] = true; // 标记字母出现过
dout[a]++; // 首字母出度加 1
din[b]++; // 尾字母入度加 1
p[find(a)] = find(b); // 合并首尾字母所在的集合
}
// ========= 检查度数条件 =========
int start = 0; // 出度比入度多 1 的节点数(起点)
int end = 0; // 入度比出度多 1 的节点数(终点)
bool success = true;
for (int i = 0; i < 26; i++)
{
if (din[i] != dout[i])
{
if (din[i] == dout[i] + 1) // 入度比出度多 1 → 终点
end++;
else if (din[i] + 1 == dout[i]) // 出度比入度多 1 → 起点
start++;
else // 度数差超过 1 → 不可能
{
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]) // 如果该字母出现过
{
if (rep == -1)
rep = find(i); // 记录第一个出现的字母的根节点
else if (rep != find(i)) // 如果不在同一个集合中
{
success = false; // 不连通
break;
}
}
}
// ========= 输出结果 =========
if (success)
cout << "Ordering is possible." << endl;
else
cout << "The door cannot be opened." << endl;
}
return 0;
}
【运行结果】
3
2
acm
ibm
The door cannot be opened.
3
acm
malform
mouse
Ordering is possible.
2
ok
ok
The door cannot be opened.
浙公网安备 33010602011771号