1 条题解
-
0
自动搬运
来自洛谷,原作者为

loc_equinox
六次曲线!搬运于
2025-08-24 21:21:16,当前版本为作者最后更新于2020-07-24 22:28:12,作者可能在搬运后再次修改,您可在原文处查看最新版自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多
以下是正文
说在前面的话:
在我写这篇题解时,因为数据
太水问题,过审的多数题解都可以被 Hack,带来了许多困扰。所以希望这篇文章能给大家带来帮助。(欢迎各位大佬
Hack指正)
进入正题:
读题,容易想到的是以单词为点,通过末字母与首字母比较来连边,最后 DFS 寻找字典序最小的词链。这里因为要求“字典序最小”,所以要先排序。
但是这么写的缺点就在于,起始点是不确定的,所以要挨个找起点就十分低效。写出来了也会有两个点超时。
于是想到题目所求词链的特征:“单词在词链中出现且仅出现一次”。联想到欧拉路的特征,也是经过图的每一条边且只经过一次。所以考虑构造一张图,寻找其欧拉路(这里包括欧拉通路和欧拉回路)。
构造图的时候,以单词为有向边,字母为顶点。比如单词 abc,就是从顶点 a 指向顶点 c 的边之一。当一个单词的首末字母相同时,它就是一个自环。建图完毕后,先判断是否存在欧拉路,再 DFS 寻找字典序最小的欧拉路。
接下来分析代码:
Part 1:读入,建图
在建边的同时并查集,最后判断集合个数是不是 ,即判断所有点是不是都在一个集合内。这也是第一次判断欧拉路是否存在。
//注意:这只是部分代码 int n,i,letter[27],in[27],out[27],fa[27],set_count; string s[1002]; int ch_start,ch_end,stf,edf; vector<vector<node> >E; bool cmp(string a,string b) { return a<b; } int find(int x) { if(fa[x]!=x)return fa[x]=find(fa[x]); return fa[x]; } void unionn(int x,int y) { fa[y]=x; return; } struct node { int to,ord; string word; }; int main() { cin>>n; E.resize(27);//E数组存边 for(i=1;i<=n;i++)cin>>s[i]; sort(s+1,s+n+1,cmp);//排序 for(i=1;i<=n;i++) { ch_start=s[i][0]-'a'+1;//第i个词的首字母 ch_end=s[i][s[i].length()-1]-'a'+1;//第i个词的末字母 out[ch_start]++;//首字母出度加1 in[ch_end]++;//末字母入度加1 if(!letter[ch_start])//如果这个首字母(节点)没有出现过 { set_count++;//集合数加1 letter[ch_start]=1;//标记一下 fa[ch_start]=ch_start;//并查集初始化 } if(!letter[ch_end])//同上 { set_count++; letter[ch_end]=1; fa[ch_end]=ch_end; } if(ch_start!=ch_end)//如果不是自环 { stf=find(ch_start); edf=find(ch_end); //找代表元 if(stf!=edf)//如果不在同一集合内 { set_count--;//集合数减1 unionn(stf,edf); } } node tmp;//新建一条边 tmp.to=ch_end; tmp.ord=i; tmp.word=s[i]; E[ch_start].push_back(tmp);//vector存图 } if(set_count!=1)//如果存在多个集合(连通块),那么一定没有欧拉路 { cout<<"***"; return 0; }
Part 2:寻找欧拉路起始点
有向图中欧拉通路存在条件是:有且仅有一个点出度比入度大 (起点),有且仅有一个点入度比出度大 (终点),其余点入度等于出度。
有向图中欧拉回路存在条件是:所有点入度等于出度。
结合上述判定方法,有如下代码:
int Eular_start,Eular_end; //... for(i=1;i<=26;i++)//最多只有26个点 { if(!letter[i])continue;//如果这个字母没有出现就跳过 if(out[i]==in[i]+1)//这个点出度比入度大1 { if(Eular_start)//如果有多个起点,那么无解 { cout<<"***"; return 0; } Eular_start=i;//标记起点 } else if(in[i]==out[i]+1)//同上 { if(Eular_end) { cout<<"***"; return 0; } Eular_end=i; } else if(in[i]==out[i])continue; else { cout<<"***"; return 0; } } if((Eular_start&&!Eular_end)||(!Eular_start&&Eular_end))//如果“有始无终”或“有终无始”,则无解 { cout<<"***"; return 0; } if(!Eular_start)Eular_start=s[1][0]-'a'+1;//如果是回路(无起点),则选字典序最小的点出发
Part 3:DFS求欧拉路
因为每个点出边已经排好了序,所以顺序遍历即可。在搜索时顺便记录答案。
int vis[1002]; string res[1002]; void dfs(int st,int now,int pre_edge)//st表示词链中有几个单词,now表示现在到达了哪一个点,pre_edge表示上一条边的序号,方便回溯 { if(st==n)//如果到达终点 { for(i=1;i<=n;i++)//输出结果 { cout<<res[i]; if(i<n)cout<<"."; } exit(0); } for(int k=0;k<E[now].size();k++) { if(!vis[E[now][k].ord])//如果未被标记过 { vis[E[now][k].ord]=1; res[st+1]=E[now][k].word; dfs(st+1,E[now][k].to,E[now][k].ord); } } vis[pre_edge]=0;//回溯 return; } //... dfs(0,Eular_start,0);//从Part 2中找到的起点开始
(完整代码在这里)
- 1
信息
- ID
- 129
- 时间
- 1000ms
- 内存
- 128MiB
- 难度
- 5
- 标签
- 递交数
- 1
- 已通过
- 0
- 上传者