当前位置: 首页 > news >正文

【提高+/省选−】洛谷P1127 ——词链

见:P1127 词链 - 洛谷

题目描述

如果单词 X 的末字母与单词 Y 的首字母相同,则 X 与 Y 可以相连成 X.Y。(注意:X、Y 之间是英文的句号 .)。例如,单词 dog 与单词 gopher,则 dog 与 gopher 可以相连成 dog.gopher

另外还有一些例子:

  • dog.gopher
  • gopher.rat
  • rat.tiger
  • aloha.aloha
  • arachnid.dog

连接成的词可以与其他单词相连,组成更长的词链,例如:

aloha.arachnid.dog.gopher.rat.tiger

注意到,. 两边的字母一定是相同的。

现在给你一些单词,请你找到字典序最小的词链,使得每个单词在词链中出现且仅出现一次。注意,相同的单词若出现了 k 次就需要输出 k 次。

输入格式

第一行是一个正整数 n(1≤n≤1000),代表单词数量。

接下来共有 n 行,每行是一个由 1 到 20 个小写字母组成的单词。

输出格式

只有一行,表示组成字典序最小的词链,若不存在则只输出三个星号 ***

输入输出样例

in:
6
aloha
arachnid
dog
gopher
rat
tigerout:
aloha.arachnid.dog.gopher.rat.tiger

说明/提示

  • 对于 40% 的数据,有 n≤10;
  • 对于 100% 的数据,有 n≤1000。

这道题好难啊╥﹏╥

整体思路

这个问题本质上是在有向图中寻找欧拉路径。每个字符串可以看作图中的一条边,边的起点是字符串的首字符,终点是字符串的尾字符。我们需要判断:

  1. 是否存在欧拉路径
  2. 如果存在,找出具体的路径

code

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
string a[maxn];
string ans[maxn];
string now[maxn];
int sum=0;
int len[maxn];
int book[maxn];
map<char,int> s1,s2;
int n;
int flag=0;
void dfs(int last,int step) {if(flag==1)return;if(step==n) {flag=1;for(int i=1; i<=sum; i++) {ans[i]=now[i];}return;}for(int i=1; i<=n; i++) {if(book[i]==1)continue;if(a[last][a[last].length()-1]==a[i][0]) {now[++sum]=a[i];book[i]=1;dfs(i,step+1);sum--;book[i]=0;}}
}
int main() {scanf("%d",&n);for(int i=1; i<=n; i++) {cin>>a[i];len[i]=a[i].length();s1[a[i][0]]++;s2[a[i][len[i]-1]]++;}int start=1;sort(a+1,a+1+n);char s,t;for(char c='a'; c<='z'; c++) {if(abs(s1[c]-s2[c])==1) {if(s1[c]-s2[c]==1)s=c;else if(s2[c]-s1[c]==1)t=c;}}int cnt=s2[t];for(int i=1; i<=n; i++) {if(a[i][0]==s && (a[i][len[i]-1]!=t || cnt!=1)) {start=i;break;}}book[start]=1;now[++sum]=a[start];dfs(start,1);if(flag==0) {printf("***\n");return 0;}for(int i=1; i<=n; i++) {if(i!=n)cout<<ans[i]<<".";elsecout<<ans[i];}return 0;
}

能理解吗?

不能的话来分析一下    

——————————————————————————————————-——————— 

代码详细解析

#include<bits/stdc++.h>
using namespace std;

这两行代码包含了所有标准库头文件并使用标准命名空间,简化了后续代码的编写。

数据结构与全局变量

const int maxn=1e5+5;
string a[maxn];     // 存储输入的所有字符串
string ans[maxn];   // 存储最终的合法接龙序列
string now[maxn];   // 存储当前DFS搜索路径中的接龙序列
int sum=0;          // 当前路径中的字符串数量
int len[maxn];      // 每个字符串的长度
int book[maxn];     // 标记字符串是否已被使用(1表示已使用,0表示未使用)
map<char,int> s1,s2;// 统计每个字符作为字符串开头和结尾的次数
int n;              // 字符串数量
int flag=0;         // 标记是否已找到合法解(1表示找到,0表示未找到)

输入处理与预处理

int main() {scanf("%d",&n);  // 读取单词数量for(int i=1; i<=n; i++) {cin>>a[i];  // 读取每个单词len[i]=a[i].length();  // 记录单词长度s1[a[i][0]]++;  // 统计首字母出现次数s2[a[i][len[i]-1]]++;  // 统计尾字母出现次数}int start=1;  // 默认从第一个单词开始搜索sort(a+1,a+1+n);  // 对单词进行字典序排序,确保找到的解是字典序最小的

这里使用两个map来统计每个字符作为字符串开头和结尾的次数,

为后续判断欧拉路径做准备。

例如:

  • 如果输入字符串为 ["abc", "cde", "efg"]
  • s1为 {'a':1, 'c':1, 'e':1}
  • s2为 {'c':1, 'e':1, 'g':1}

欧拉路径判断

​sort(a+1,a+1+n);  // 先排序,确保字典序最小
char s,t;
for(char c='a';c<='z';c++)
{if(abs(s1[c]-s2[c])==1){if(s1[c]-s2[c]==1)s=c;  // 起点字符(出度比入度多1)elseif(s2[c]-s1[c]==1)t=c;  // 终点字符(入度比出度多1)}
}
int cnt=s2[t];

这部分代码判断是否存在欧拉路径:

  • 欧拉路径条件
    1. 有向图中,所有节点的入度等于出度,或者
    2. 存在一个节点的出度比入度多 1 (起点),一个节点的入度比出度多 1 (终点),其余节点的入度等于出度
  • s是起点字符,t是终点字符
  • cnt记录终点字符t作为结尾的次数

确定搜索起点

​int start=1;
for(int i=1;i<=n;i++)
{if(a[i][0]==s && (a[i][len[i]-1]!=t || cnt!=1)){start=i;break;}
}
book[start]=1;
now[++sum]=a[start];

这部分代码确定 DFS 的起始字符串:

  • 优先选择以s开头且不以t结尾的字符串作为起点
  • 如果所有以s开头的字符串都以t结尾,则选择其中任意一个
  • 标记该字符串为已使用,并加入当前路径

深度优先搜索(核心逻辑)

​void dfs(int last,int step)
{if(flag==1)  // 已找到解,直接返回return;if(step==n)  // 已找到n个字符串的合法序列{flag=1;for(int i=1;i<=sum;i++){ans[i]=now[i];  // 保存当前路径到结果数组}return;}for(int i=1;i<=n;i++){if(book[i]==1)  // 跳过已使用的字符串continue;if(a[last][a[last].length()-1]==a[i][0])  // 当前字符串的结尾等于下一个的开头{now[++sum]=a[i];  // 加入当前路径book[i]=1;        // 标记为已使用dfs(i,step+1);    // 递归搜索sum--;            // 回溯book[i]=0;        // 撤销标记}}
}

这是典型的回溯 DFS 算法:

  • 参数说明
    • last:当前路径中最后一个字符串的索引
    • step:当前路径的长度
  • 终止条件
    • 当路径长度达到 n 时,表示找到合法解
    • 将当前路径保存到ans数组中,并标记flag=1
  • 递归过程
    • 遍历所有未使用的字符串
    • 选择下一个能与当前字符串首尾相连的字符串
    • 标记该字符串为已使用,加入路径,递归搜索
    • 回溯操作:撤销当前选择,尝试其他可能性

 输入处理与预处理

int main() {scanf("%d",&n);  // 读取单词数量for(int i=1; i<=n; i++) {cin>>a[i];  // 读取每个单词len[i]=a[i].length();  // 记录单词长度s1[a[i][0]]++;  // 统计首字母出现次数s2[a[i][len[i]-1]]++;  // 统计尾字母出现次数}int start=1;  // 默认从第一个单词开始搜索sort(a+1,a+1+n);  // 对单词进行字典序排序,确保找到的解是字典序最小的

这里使用两个map来统计每个字符作为字符串开头和结尾的次数,

为后续判断欧拉路径做准备。

例如:

  • 如果输入字符串为 ["abc", "cde", "efg"]
  • s1为 {'a':1, 'c':1, 'e':1}
  • s2为 {'c':1, 'e':1, 'g':1}

欧拉路径判断

    char s, t;for(char c='a'; c<='z'; c++) {if(abs(s1[c]-s2[c])==1) {  // 找到入度和出度差为1的字母if(s1[c]-s2[c]==1)s=c;  // 首字母次数比尾字母多1的字母,作为序列的起始字母else if(s2[c]-s1[c]==1)t=c;  // 尾字母次数比首字母多1的字母,作为序列的结束字母}}int cnt=s2[t];  // 结束字母t作为尾字母的次数for(int i=1; i<=n; i++) {if(a[i][0]==s && (a[i][len[i]-1]!=t || cnt!=1)) {start=i;  // 找到以s开头且不以t结尾的单词,或者t作为尾字母仅出现一次的单词,作为起始单词break;}}

这部分代码判断是否存在欧拉路径:

  • 欧拉路径条件
    1. 有向图中,所有节点的入度等于出度,或者
    2. 存在一个节点的出度比入度多 1 (起点),一个节点的入度比出度多 1 (终点),其余节点的入度等于出度
  • s是起点字符,t是终点字符
  • cnt记录终点字符t作为结尾的次数

确定搜索起点

    book[start]=1;  // 标记起始单词已使用now[++sum]=a[start];  // 将起始单词加入当前路径dfs(start,1);  // 从起始单词开始DFS搜索if(flag==0) {  // 如果未找到合法解printf("***\n");return 0;}for(int i=1; i<=n; i++) {  // 输出找到的合法解if(i!=n)cout<<ans[i]<<".";  // 单词之间用点连接elsecout<<ans[i];}printf("\n");return 0;
}

这部分代码确定 DFS 的起始字符串:

  • 优先选择以s开头且不以t结尾的字符串作为起点
  • 如果所有以s开头的字符串都以t结尾,则选择其中任意一个
  • 标记该字符串为已使用,并加入当前路径

输出结果

​if(flag==0)
{printf("***\n");  // 无法形成合法接龙return 0;
}
for(int i=1;i<=n;i++)
{if(i!=n)cout<<ans[i]<<".";  // 用点连接各个字符串elsecout<<ans[i];
}

算法关键点

  1. 欧拉路径判断

    • 通过统计字符的开头和结尾次数,判断是否满足欧拉路径条件
    • 确定起点和终点字符
  2. DFS 回溯搜索

    • 在满足欧拉路径条件的前提下,通过 DFS 找到具体的字符串序列
    • 使用回溯确保所有可能性被探索
  3. 字典序优化

    • 预先对字符串数组排序,确保优先选择字典序小的字符串
    • 起点选择策略保证了在多个可能的起点中选择字典序最小的路径

复杂度分析

  • 时间复杂度:最坏情况下是 O (n!),但通过欧拉路径的判断和起点优化,实际运行效率会高很多
  • 空间复杂度:O (n),主要用于存储字符串和递归调用栈

这个算法巧妙地将字符串接龙问题转化为图论中的欧拉路径问题,并通过 DFS 回溯搜索找到具体解,是一道非常经典的算法题。

———————————————————————————————————————————

好了

到此结束吧

———————————————————————————————————————————

THE END

对了

听说给点赞+关注+收藏的人会发大财哦(o゚▽゚)o  

相关文章:

  • 《算法笔记》之二(笔记)
  • 63、不同路径II
  • ✨通义万相 2.1(Wan2.1)环境搭建指南:基于 CUDA 12.4 + Python 3.11 + PyTorch 2.5.1 GPU加速实战
  • 排序算法-python实现
  • C++入门实战--通讯录管理系统
  • 【闲谈】对于c++未来的看法
  • JAVA集合篇--深入理解ConcurrentHashMap图解版
  • git安装使用详细教程
  • MySQL之索引结构和分类深度详解
  • 零基础学习Redis(13) -- Java使用Redis命令
  • Java基础 6.22
  • 【学习笔记】深入理解Java虚拟机学习笔记——第10章 前端编译与优化
  • 系列一、windows中安装RabbitMQ
  • 硬件面经-具身机器人通用技术要求
  • 逻辑门电路Multisim电路仿真汇总——硬件工程师笔记
  • 信息安全管理与评估2025湖北卷路由部分答案
  • pyhton基础【14】函数四
  • 爬虫001----介绍以及可能需要使用的技术栈
  • multiprocessing.pool和multiprocessing.Process
  • DeepSeek:中国AI开源先锋的技术突破与行业革新
  • 怎么查找网站/百度首页推广广告怎么做
  • 基于.net平台网站内容管理系统研究与实现/深圳营销策划公司十强
  • 展示型网站设计与制作团队/刚刚发生 北京严重发生
  • 深圳行业网站建设/品牌推广的意义
  • 盘州网站建设/湖南seo技术培训
  • 苏州园区网站建设公司/湖南网络推广排名