题解: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.

【核心思想】

  1. 问题分析:给定 \(N\) 个单词,需要安排顺序使得相邻单词中前一个的末字母等于后一个的首字母。将每个单词看作一条从首字母指向尾字母的有向边,问题转化为判断该有向图中是否存在欧拉路径(经过每条边恰好一次的路径)。这是一个欧拉路径判定问题,关键在于检查图的连通性和度数条件。

  2. 算法选择

    • 并查集:检查所有出现过的字母是否在同一个连通分量中
    • 入度/出度统计:统计每个字母作为单词开头(出度)和结尾(入度)的次数
    • 欧拉路径判定条件:连通且满足特定的度数条件
  3. 关键步骤

    • 建图:读取每个单词,首字母为 \(a\),尾字母为 \(b\)
      • dout[a]++:首字母出度加1
      • din[b]++:尾字母入度加1
      • p[find(a)] = find(b):并查集合并首尾字母
    • 检查度数条件
      • 遍历26个字母,统计入度不等于出度的节点
      • din[i] == dout[i] + 1:入度比出度多1,作为终点
      • din[i] + 1 == dout[i]:出度比入度多1,作为起点
      • 其他情况:度数差超过1,不可能存在欧拉路径
      • 合法条件:起点和终点都为0(欧拉回路),或起点和终点都为1(欧拉路径)
    • 检查连通性
      • 遍历所有出现过的字母,检查是否属于同一个并查集集合
    • 输出结果:若度数条件和连通性都满足,则存在合法解
  4. 时间/空间复杂度

    • 时间复杂度:\(O(T \times (N \alpha(26) + 26))\),其中 \(T\) 为测试组数,\(\alpha\) 为阿克曼函数的反函数,可视为常数
    • 空间复杂度:\(O(26)\),只需维护26个字母的度数和并查集
  5. 欧拉路径的核心思想

    • 问题转化:将单词接龙问题转化为有向图的欧拉路径问题,每个单词是一条有向边
    • 度数守恒:欧拉回路要求所有点入度等于出度;欧拉路径要求恰好一个起点(出度比入度多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.
posted @ 2026-06-18 18:03  团爸讲算法  阅读(4)  评论(0)    收藏  举报