[luogu 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
注意到,.
两边的字母一定是相同的。
现在给你一些单词,请你找到字典序最小的词链,使得这些单词在词链中出现且仅出现一次。
输入输出格式
输入格式
第一行是一个正整数 \(n\)(\(1 \le n \le 1000\)),代表单词数量。
接下来共有 \(n\) 行,每行是一个由 \(1\) 到 \(20\) 个小写字母组成的单词。
输出格式
只有一行,表示组成字典序最小的词链,若不存在则只输出三个星号 ***
。
输入输出样例
输入样例 #1
6
aloha
arachnid
dog
gopher
rat
tiger
输出样例 #1
aloha.arachnid.dog.gopher.rat.tiger
说明
- 对于 \(40\%\) 的数据,有 \(n \leq 10\);
- 对于 \(100\%\) 的数据,有 \(n \leq 1000\)。
分析
此题是一道挺复杂的搜索,推荐一做,挺考验码力。
首先,我们可以把字母当成点,单词当成边。
比如,word,就可以代表 \(w \rightarrow d\) 这条边。然后dfs寻找最小的词链。但是啊同志,\(n \le 1000\),这个复杂度裸爆搜,时间复杂度很危险。
所以我们需要经过一个优化:欧拉路找起点优化。
首先,原题中要求词链中的词出现并且仅出现一次,说明题目是让我们找一条欧拉路径。
对于非欧拉回路的欧拉路径,一定满足一个点的出度比入度大1,一个点的入度比出度大1,其余点入度与出度均相等。
而对于欧拉回路,一定满足所有点的入度与出度均相等。
因此,在找起点的时候,我们可以遍历所有点,如果发现一个出度比入度大1的点,就可以设置其为源点 \(S\),如果发现一个入度比出度大1的点,那么它就是终点 \(E\)。
- 如果在遍历过程中发现两个 \(S\),无满足的词链;
- 如果在遍历过程中发现两个 \(E\),无满足的词链;
- 如果在遍历过程中,发现一个点的入度和出度的关系既不是差1又不是相等,无满足的词链;
- 如果遍历完成后,有 \(S\) 无 \(E\),无满足的词链;
- 如果遍历完成后,有 \(E\) 无 \(S\),无满足的词链;
- 以上判断结束后,如果真的没有 \(S\),说明这个图为欧拉回路,找一个字典序最小的单词开始dfs即可。
你看,直接判断掉这么多不满足的图,是不是感觉倍赚呀~所以欧拉回路和路径的性质有时真的很有用。
但是这样,就能直接开始dfs了吗?
那,如果这个图不联通呢?
哈哈哈没错,还有这种情况,那这个怎么解决呢?
并查集。
我们将所有在词首和词末出现的字母都新建一个代表字母的节点,并且如果有边那么就将他们合并,最后检查集合数是否为 \(1\) 即可。如果为 \(1\),说明联通;如果不是,那就没解了。
好了,经过这么多筛选了,没有任何无解情况了,终于可以开始欢乐的dfs了!
dfs前要把所有字符串按照字典序排列好哦。
dfs的话。。实在是没啥好讲的了。直接套模板就行了。
那就上代码吧!
代码
/*
* @Author: crab-in-the-northeast
* @Date: 2020-09-26 13:17:28
* @Last Modified by: crab-in-the-northeast
* @Last Modified time: 2020-09-26 20:06:22
*/
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <vector>
const int maxn = 1005;
const int maxl = 30;
struct node {
int v, id;
std :: string word;
};
std :: vector <node> G[maxl];
int ind[maxl], outd[maxl];
bool letter[maxl], vis[maxn];
int n;
std :: string str[maxn], ans[maxn];
int cti(char c) {
return c - 'a' + 1;
}
void GG() {
puts("***");
exit(0);
}
struct ufs {
int fa[10086];
bool valid[10086];
int siz;
void init(int n) {
siz = n;
for (int i = 1; i <= n; ++i)
fa[i] = i;
}
int find(int x) {
while (x != fa[x]) x = fa[x] = fa[fa[x]];
return x;
}
void unite(int x, int y) {
fa[find(x)] = find(y);
}
bool judge(int x, int y) {
return find(x) == find(y);
}
int get_setnum() {
int setnum = 0;
for (int i = 1; i <= siz; ++i)
if (fa[i] == i && valid[i])
++setnum;
return setnum;
}
};
void dfs(int u, int step) {
if (step == n) {
std :: printf("%s", ans[1].c_str());
for (int i = 2; i <= n; ++i)
std :: printf(".%s", ans[i].c_str());
exit(0);
}
for (int i = 0; i < G[u].size(); ++i) {
node e = G[u][i];
if (!vis[e.id]) {
vis[e.id] = true;
ans[step + 1] = e.word;
dfs(e.v, step + 1);
vis[e.id] = false;
}
}
return ;
}
int main() {
std :: scanf("%d", &n);
for (int i = 1; i <= n; ++i)
std :: cin >> str[i];
std :: sort(str + 1, str + 1 + n);
ufs Gu;
Gu.init(maxl);
for (int i = 1; i <= n; ++i) {
int first = cti(str[i][0]);
int last = cti(str[i][str[i].length() - 1]);
++ind[last];
++outd[first];
Gu.valid[last] = true;
Gu.valid[first] = true;
if (first != last) {
if (!Gu.judge(first, last))
Gu.unite(first, last);
}
G[first].push_back((node){last, i, str[i]});
}
if (Gu.get_setnum() != 1)
GG();
for (int i = 1; i <= 26; ++i)
letter[i] = Gu.valid[i];
//puts("Ok");
int S = 0, E = 0;
for (int i = 1; i <= 26; ++i) {
if (!letter[i]) continue;
if (outd[i] == ind[i] + 1) {
if (S)
GG();
S = i;
} else if (ind[i] == outd[i] + 1) {
if (E)
GG();
E = i;
} else if (ind[i] == outd[i]) {
continue;
} else {
GG();
}
}
if ((S && !E) || (!S && E))
GG();
if (!S) S = cti(str[1][0]);
dfs(S, 0);
return 0;
}