单词拼接
题目链接:点击打开链接
描述
给你一些单词,请你判断能否把它们首尾串起来串成一串。
前一个单词的结尾应该与下一个单词的道字母相同。
如
aloha
dog
arachnid
gopher
tiger
rat
可以拼接成:aloha.arachnid.dog.gopher.rat.tiger
-
输入
-
第一行是一个整数N(0<N<20),表示测试数据的组数
每组测试数据的第一行是一个整数M,表示该组测试数据中有M(2<M<1000)个互不相同的单词,随后的M行,每行是一个长度不超过30的单词,单词全部由小写字母组成。
输出
-
如果存在拼接方案,请输出所有拼接方案中字典序最小的方案。(两个单词之间输出一个英文句号".")
如果不存在拼接方案,则输出
***
样例输入
-
2 6 aloha arachnid dog gopher rat tiger 3 oak maple elm
样例输出
-
aloha.arachnid.dog.gopher.rat.tiger ***
分析:
本题是可以将单词看成是一条边,而首尾的两个可以看成是两个点,每一条边连接着首尾的两个单词,而根据题中的意思,我们需要找到一条欧拉路,让每条边都走一遍,但是也能存在有环的情况,所以对于每个点的度我们都要有所记录,可以当做判断的条件,因为可以成环,也可以不成环,如果不成环,那么肯定有一个点的出度比入度大一,有一个点的入度比出度大一,在这种情况下,是不是我们就可以找到开始点了,于是再将所有的单词按字典树的顺序排好序,进行 dfs ,如若存在符合题意的序列就可将其下标保存在一个数组中,然后将输出,如果成环的话,那么我们就找字典序排列最小的且有出度的点,再进行 dfs ,如果对 dfs 不是太熟悉,感觉或许有点迷,如果不太了解欧拉路,那就自己百度,其实很简单,不要想得太复杂,比如规定的点的度,我们只需要判断其度是否符合要求即可,实在不行的话,可以手动模拟,但是不要不动手就将过程中的点想的那么复杂,好了,我们来看看代码吧,在代码中需要注意的地方,我会写上详细的注释哦。。
在这里解释代码中注释的不详细的名词:
相同点:点,指的是单词的首和尾,相同的点指的是单词中相同的首字母或尾字母,可能不是出现在同一个单词中,这样统计不会有影响
#include <iostream>
#include<stdio.h>
#include<string>
#include<string.h>
#include<algorithm>
#include<math.h>
using namespace std;
struct book{
char w[35];///存单词
int s,e;///村单词的首字母和尾字母
}h[1005];
bool cmp(struct book a,struct book b)///将单词排序
{
return strcmp(a.w,b.w)<0;
}
int n,m;
int outdegree[1005],indegree[1005],vis[1005],pre[1005];
int judge()///判断点的出度和入度,看是否符合欧拉回路
{
int outdegree1=0,indegree1=0,bj=0;
for(int i=0;i<26;i++)///将所有点的情况进行统计
{
if(abs(outdegree[i]-indegree[i])>=2)///此情况说明不符合欧拉回路
return -1;
else if(outdegree[i]-indegree[i]==1)///此情况很能是起始点,有出度没有入度
{
outdegree1++;///记录所有点多出的出度
bj=i;
}
else if(outdegree[i]-indegree[i]==-1)///此情况很可能是终点,有入度没有出度
indegree1++;///记录所有点多出的入度
}
if(indegree1>1||outdegree1>1)///此情况说明不符合欧拉路
return -1;
else if(outdegree-indegree==0)///此情况说明是欧拉回路
{
for(int i=0;i<26;i++)
{
if(outdegree[i])///则要返回的是字典树最小且有出度的单词,将其作为起点
return i;
}
}
else
return bj;///如果不成环,就返回起点
}
int dfs(int k,int num)///找是否有符合题意的排列
{
if(num==m)///如果等于m,说明我们已经找到符合题意的顺序了
return 1;
for(int i=0;i<m;i++)///遍历m个单词
{
if(vis[i]||h[i].s<k)///说明次单词不可用
continue;
else if(h[i].s>k)///说明已经没有以单词k为首字母的单词可用了,因为我们排过序了
return 0;
else if(h[i].s==k)///说明找到可用的单词了
{
vis[i]=1;///将次单词标记
pre[num]=i;///将其下标存着
/**接下来的dfs尤为注意,我之前没有将它放进if里判断,然后错了,因为我们本题要找的是序列,
如果不判断直接dfs,然后接着就将标记取消的话,可能有些单词我们已经找到它的位置了,回溯的时候
将标记都取消的话,意味着我们像找有多少条路径能出迷宫一样,那不适用于本题,本题只需要将找不到
以该单词的末尾单词为首元素并且还有没有排序的单词的单词取消标记,再重新调整位置而已,这里要仔细理解**/
if(dfs(h[i].e,num+1))
return 1;
vis[i]=0;
}
}
return 0;
}
int main()
{
scanf("%d",&n);
while(n--)
{
scanf("%d",&m);
memset(outdegree,0,sizeof(outdegree));///存相同点的出度
memset(indegree,0,sizeof(indegree));///存相同点的入度
for(int i=0;i<m;i++)
{
scanf("%s",&h[i].w);
int len=strlen(h[i].w);
h[i].s=h[i].w[0]-'a';///存首字母
h[i].e=h[i].w[len-1]-'a';///存尾字母
outdegree[h[i].s]++;///对应的出度和入度++
indegree[h[i].e]++;
}
int ok=judge();/**接受judge()函数返回的参数,刚开始我将判断错误的返回0,
后来想不对,因为正确的时候返回也有可能是0,因为下标是从0开始的**/
if(ok==-1)///说明判断点的出度入度就不符合欧拉图
{
printf("***\n");
continue;
}
sort(h,h+m,cmp);///将单词进行字典序的顺序排列
memset(vis,0,sizeof(vis));///将标记数组初始化
if(!dfs(ok,0))///如果不能找到能将所有的单词首尾相接的话
{
printf("***\n");
continue;
}
for(int i=0;i<m;i++)///按照dfs中pre数组保存的序号输出h.w中对应的单词即可
{
if(i==0)
printf("%s",h[pre[i]].w);
else
printf(".%s",h[pre[i]].w);
}
printf("\n");
}
return 0;
}