【问题描述】
在一种未知语言中,很多单词被发现了,但是他们的字母的字典序我们是不知道的。我们知道的是,这些单词是按照字典序从小到大排列的。
或者找出这种语言唯一的字母的字典序,或者得出这种方案是不存在的,或者得出有很多种这样的方案。
【输入格式】
第一行包括一个正整数n(1 <= n <= 100),表明单词的数量。
接下来n行,每行一个单词,每个单词最多包含10个小写的英文字母。保证单词以未知语言的字典序给出。
【输出格式】
有且仅有一行,输出包含所有字母的字典序。如果没有这种字典序,则输出“!”,如果有多种方案则输出“?”。
【输入样例1】
5
ula
uka
klua
kula
al
【输出样例1】
luka
【输入样例2】
4
jaja
baba
baja
beba
【输出样例2】
!
【输入样例3】
3
marko
darko
zarko
【输出样例3】
?
【数据范围与约定】
对于30%的数据:n <= 20。
对于100%的数据:n <= 100。
【解法1】拓扑排序
- 确定一个唯一的字典序,我们可以想到拓扑排序
- 若两个字符串 si,sj(i<j) 已经按照字典序从小到大排序,那么我们找到它们从起始位置开始的第一个不同的字母,则这两个字母 si[k],sj[k] 也满足从小到大的字典序,建边 si[k]→sj[k] 。
- 然后对这样建成的图进行拓扑排序
- 若图中存在环,则字典序中存在自相矛盾的情况,需要输出”!”,这时拓扑排序显然是无法遍历全部点的,我们记录总的点数以及遍历到的点数加以判断
- 若通过某一个点更新其它点的入度时,发现此时存在多个点入度为0,则这些点的顺序是不能确定的,需要输出”?”,但要注意若已经发现上述”!”的情况,则应优先输出”!”
- 若上述两种情况都未出现,直接输出拓扑序。
【代码1】
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int N = 105, M = N * N;
char s[N][15]; bool G[27][27], vis[27], sf;
int E, n, top, stp;
int l[N], stk[N], pos[N], rin[N];
inline int Min(const int &x, const int &y){return x < y ? x : y;}
struct Edge
{
int to; Edge *nxt;
}a[M], *T = a, *lst[N];
inline void addEdge(const int &x, const int &y)
{
(++T)->nxt = lst[x]; lst[x] = T; T->to = y; rin[y]++;
}
int main()
{
freopen("words.in", "r", stdin);
freopen("words.out", "w", stdout);
scanf("%d", &n);
for (int i = 1; i <= n; ++i)
scanf("%s", s[i]), l[i] = strlen(s[i]);
for (int i = 1; i <= n; ++i)
for (int j = i + 1; j <= n; ++j)
{
int ml = Min(l[i], l[j]);
for (int k = 0; k < ml; ++k)
{
int tx = s[i][k] - 'a',
ty = s[j][k] - 'a';
if (tx == ty) continue;
if (!G[tx][ty]) addEdge(tx, ty);
G[tx][ty] = true;
break;
}
for (int k = 0; k < l[i]; ++k) vis[s[i][k] - 'a'] = true;
for (int k = 0; k < l[j]; ++k) vis[s[j][k] - 'a'] = true;
}
/*
for (int i = 0; i < 26; ++i)
for (Edge *e = lst[i]; e; e = e->nxt)
printf("%c -> %c\n", i + 'a', e->to + 'a');
*/
int x, y, cnt = 0, num = 0;
for (int i = 0; i < 26; ++i)
if (vis[i])
{
stp++;
if (!rin[i])
{
stk[++top] = pos[++E] = i; cnt++;
if (cnt > 1) sf = true;
}
}
while(top)
{
x = stk[top--]; cnt = 0; num++;
for (Edge *e = lst[x]; e; e = e->nxt)
if (!(--rin[y = e->to]))
{
cnt++; stk[++top] = pos[++E] = y;
if (cnt > 1) sf = true;
}
}
if (num != stp) puts("!");
else if (sf) puts("?");
else for (int i = 1; i <= E; ++i) putchar(pos[i] + 'a');
fclose(stdin); fclose(stdout);
return 0;
}
【解法2】Floyd
- 记
bool
数组
f[x][y]
表示字母
x
的字典序是否小于字母
y ,初始时的建图同上 - 若存在 f[x][y]=f[y][x]=1 ,则字典序中存在自相矛盾的情况,输出”!”
- 若存在 f[x][y]=f[y][x]=0 ,则字母 x,y 的字典序大小关系无法确定,可能有多种方案,输出”?”
- 否则我们根据
f[x][y]
,统计出字典序中比
x
大的字母个数,就可以确定
x 在字典序中的排名,最后按顺序输出即可
【代码2】
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int N = 105;
char s[N][15], Ans[N]; int l[N], num, n, tp;
bool f[27][27], vis[27], sf;
inline int Min(const int &x, const int &y) {return x < y ? x : y;}
int main()
{
freopen("words.in", "r", stdin);
freopen("words.out", "w", stdout);
scanf("%d", &n);
for (int i = 1; i <= n; ++i)
scanf("%s", s[i]), l[i] = strlen(s[i]);
for (int i = 1; i <= n; ++i)
for (int j = i + 1; j <= n; ++j)
{
int ml = Min(l[i], l[j]);
for (int k = 0; k < ml; ++k)
if (s[i][k] != s[j][k])
{
f[s[i][k] - 'a'][s[j][k] - 'a'] = true;
break;
}
for (int k = 0; k < l[i]; ++k) vis[s[i][k] - 'a'] = true;
for (int k = 0; k < l[j]; ++k) vis[s[j][k] - 'a'] = true;
}
for (int k = 0; k < 26; ++k)
for (int i = 0; i < 26; ++i)
for (int j = 0; j < 26; ++j)
f[i][j] |= f[i][k] & f[k][j];
for (int i = 0; i < 26; ++i)
if (vis[i])
{
int num = 0; tp++;
for (int j = 0; j < 26; ++j)
if (vis[j] && i != j)
{
if (!f[i][j] && !f[j][i]) sf = true;
if (f[i][j])
{
num++;
if (f[j][i])
{
puts("!");
fclose(stdin); fclose(stdout);
return 0;
}
}
}
Ans[num] = i + 'a';
}
if (sf) puts("?");
else for (int i = tp - 1; i >= 0; --i) putchar(Ans[i]);
fclose(stdin); fclose(stdout);
return 0;
}