LDU 2020下半年十三周训练 D 单词(拓扑排序)

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

【输入格式】
第一行包括一个正整数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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值