[日常训练] 单词

【问题描述】

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

【输入格式】

第一行包括一个正整数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;
} 
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值