【问题描述】
在一种未知语言中,很多单词被发现了,但是他们的字母的字典序我们是不知道的。我们知道的是,这些单词是按照字典序从小到大排列的。
或者找出这种语言唯一的字母的字典序,或者得出这种方案是不存在的,或者得出有很多种这样的方案。

【输入格式】
第一行包括一个正整数n(1 <= n <= 100),表明单词的数量。
接下来n行,每行一个单词,每个单词最多包含10个小写的英文字母。保证单词以未知语言的字典序给出。

【输出格式】
有且仅有一行,输出包含所有字母的字典序。如果没有这种字典序,则输出“!”,如果有多种方案则输出“?”。

【输入样例1】
5
ula
uka
klua
kula
al
【输出样例1】
luka

思路

确定一个唯一的字典序,我们可以想到拓扑排序
若两个字符串si,sj(i<j)已经按照字典序从小到大排序,那么我们找到它们从起始位置开始的第一个不同的字母,则这两个字母si[k],sj[k]也满足从小到大的字典序,建边si[k]→sj[k]。
然后对这样建成的图进行拓扑排序
若图中存在环,则字典序中存在自相矛盾的情况,需要输出”!”,这时拓扑排序显然是无法遍历全部点的,我们记录总的点数以及遍历到的点数加以判断
若通过某一个点更新其它点的入度时,发现此时存在多个点入度为0,则这些点的顺序是不能确定的,需要输出”?”,但要注意若已经发现上述”!”的情况,则应优先输出”!”
若上述两种情况都未出现,直接输出拓扑序。

代码

vector<int>G[maxn];
int in[30];
string a;
int vis[30];
int ans[30],l=0;
int num=0;
bool flag=false;

void topsort(){
queue<int>q;
int min=inf;
int x;
for(int i=0;i<26;i++){
if(!in[i]&&vis[i]){
q.push(i);
}
}
while(!q.empty()){
int v=q.front();
q.pop();
if(q.size()){
flag=true;
}
ans[++l]=v;
num++;
//printf("%c",v+'a');
for(int i=0;i<G[v].size();i++){
in[G[v][i]]--;
if(!in[G[v][i]]){
q.push(G[v][i]);
}
}
}
}
string s[120];
int main(){
int m,n=0;
cin>>m;
memset(vis,0,sizeof vis);
for(int i=1;i<=m;i++){
cin>>s[i];
}
for(int i=1;i<=m;i++){
for(int j=i+1;j<=m;j++){
int len1=s[i].size(),len2=s[j].size();
int flag1=0;
for(int k=0;k<min(len1,len2);k++){
if(s[i][k]!=s[j][k]&&flag1==0){
in[s[j][k]-'a']++;
vis[s[j][k]-'a']=1;
vis[s[i][k]-'a']=1;
G[s[i][k]-'a'].push_back(s[j][k]-'a');
flag1=1;
}
vis[s[j][k]-'a']=1;
vis[s[i][k]-'a']=1;
}
}
}
int cnt=0;
for(int i=0;i<26;i++){
if(vis[i]){
cnt++;
}
}

topsort();

if(num!=cnt){
//cout<<num<<" "<<cnt<<endl;
puts("!");
}
else if(flag){
puts("?");
}
else{
for(int i=1;i<=l;i++){
printf("%c",ans[i]+'a');
}
}
return 0;
}