AcWing 5563. 自定义字典序----拓扑排序
题目
使用传统字典序对单词进行排序的做法如下(摘自百度百科)。
先按照第一个字母,以 \(a, b, c, …, z\) 的顺序排列;如果第一个字母一样,那么比较第二个、第三个乃至后面的字母。如果比到最后两个单词不一样长(比如,
sigh
和sight
),那么把短者排在前。
自定义字典序是指重新规定 \(a \sim z\) 的相对顺序,然后按照上述方法对单词进行排序。
给定 \(n\) 个单词在某种自定义字典序下的顺序,请你计算该自定义字典序中 \(a \sim z\) 的相对顺序。
输入格式
第一行包含整数 \(n\)。
接下来 \(n\) 行,每行包含一个由小写字母构成的字符串,表示一个单词。
所有单词两两不同,且按照自定义字典序的顺序给出。
输出格式
输出自定义字典序中 \(a \sim z\) 的相对顺序,即一个 \(a \sim z\) 的排列。
如果无解,则输出 Impossible
。
如果答案不唯一,输出任意合理答案均可。
数据范围
前 \(6\) 个测试点满足 \(1 \le n \le 10\)。
所有测试点满足 \(1 \le n \le 100\),每个单词的长度 \([1,100]\)。
输入样例1:
3
rkvist
scankr
ameinaf
输出样例1:
bcdefghijklmnopqrsatuvwxyz
输入样例2:
10
arifhba
ktaf
coqglqf
utkiarnb
stkhmjnrs
bvraaci
rrrrrrrrrrrrrrrr
bilbvfhltf
frcxjfw
ajnwtnyhnttf
输出样例2:
Impossible
输入样例3:
10
pyxd
yahd
ysciahdghs
jyjydgris
gbhryxismidhwishri
thkxti
cwgxdgmz
widixkshuoyid
odychdqialidxldsgt
namjhdyryd
输出样例3:
abdefhiklmpqrsuvxyjgtcwonz
题解
这道题审题要审清楚 题目给你的是排好序的字符串
那么我们用这几个有序的字符串就可以找到两两字符串内字母的大小关系
利用邻接矩阵 将两两字母的大小关系 例如a>b转化为从a指向b的有向边 将b的入度加1
从而最终推导出所有字母的关系
怎么判断这个字典序是有效的呢? 没有自环
我们利用拓扑排序 不仅可以将排好序的字典序输出 还可以利用队尾指针最后是不是++到了第26个字母来判断字符串有没有自环
注意 要是字典序不足26个字母 就只会排序当前字符串里有的字符
其余字母按照传统字典序排序 (感觉这个点设计的不太好)
比如输入2 a aa
输出就会直接按照传统字典序输出
特殊的数据–处理前缀问题
如果输入数据是2 aa a
即aa是a的前缀 那么这个字典序其实是不合法的因为a恒大于aa
如果不在work()里利用两字符串大小判断 那么在topsort()内我们也无法判断这个不合法的情况
因为这种情况下所有字母的入度都为0 导致q[]一开始就会全部存所有字母
即使在while(hh <= tt)的循环内我们一次也执行不了 但tt一开始就等于25
所以最终会直接按照传统字典序 输出 0~25 的所有字母
#include <bits/stdc++.h>
using namespace std;
const int N = 110, M = 26;
int n;
bool g[M][M]; //利用邻接矩阵存连个字母之间的大小关系
string s[N]; //存第i个字符串
int q[M]; //存排序好的字符串
int d[M]; //存每个字母的入度
bool work(string& a, string& b)
{
for (int i = 0; i < a.size() && i < b.size(); i ++ )
{
if (a[i] != b[i])
{
int x = a[i] - 'a', y = b[i] - 'a'; //将其转化为数字存储
g[x][y] = true;
return true;
}
}
return a < b; //如果循环内没有return 那我们就要考虑前缀问题 如果a的长度大于b 那么a就是前缀 返回false
}
bool topsort()
{
int hh = 0, tt = -1;
for (int i = 0; i < 26; i ++ )
if (!d[i])
q[++ tt] = i; //找入度为0也就是最大的字母放进队列
//cout << hh << endl;
while (hh <= tt)
{
int t = q[hh ++]; //出队
for (int i = 0; i < 26; i ++ )
{
if (g[t][i])
{
d[i] -- ; //减去当前字母的入度
if (!d[i])
q[++ tt] = i;
}
}
}
return tt == 25; //判断一下队列是不是存到第26个字母了 tt是从0开始存的 所以这里是25
}
int main()
{
scanf("%d", &n);
for (int i = 0; i < n; i ++ ) cin >> s[i]; //将每个字符串读进来
for (int i = 1; i < n; i ++ ) //由于要使用i-1号字符串 这里i从i开始
{
work(s[i - 1], s[i]); //将i-1号字符串与第i号字符串内字母进行排序
if (!work(s[i - 1], s[i])) //当s[i - 1]是s[i]的前缀时
{
puts("Impossible");
return 0;
}
}
//给每个字母的入度赋值一下 这里不直接写在work函数内是因为当两个相同的数据输入时d[j]只用++一次 放在work()内就会重复加
for (int i = 0; i < 26; i ++ )
for (int j = 0; j < 26; j ++ )
if (g[i][j])
d[j] ++ ;
if (!topsort()) puts("Impossible");
else
{
for (int i = 0; i < 26; i ++ )
cout << char(q[i] + 'a'); //以char型输出 将q[i]内数据加上a 重新变为对应字母
}
return 0;
}