Luogu P3065 [USACO12DEC]第一!First!

题目描述:、
Bessie一直在研究字符串。她发现,通过改变字母表的顺序,她可以按改变后的字母表来排列字符串(字典序大小排列)。

···例如,Bessie发现,对于字符串串“omm”,“moo”,“mom”和“ommnom”,她可以使用标准字母表使“mom”排在第一个(即字典序最小),她也可以使用字母表“abcdefghijklonmpqrstuvwxyz”使得“omm”排在第一个。然而,Bessie想不出任何方法(改变字母表顺序)使得“moo”或“ommnom”排在第一个。

接下来让我们通过重新排列字母表的顺序来计算输入中有哪些字符串可以排在第一个(即字典序最小),从而帮助Bessie。

要计算字符串X和字符串Y按照重新排列过的字母表顺序来排列
的顺序,先找到它们第一个不同的字母X[i]与Y[i],按重排后的字母表
顺序比较,若X[i]比Y[i]先,则X的字典序比Y小,即X排在Y前;若没
有不同的字母,则比较X与Y长度,若X比Y短,则X的字典序比Y小,
即X排在Y前。

输入输出格式
输入格式:

第1行:一个数字N(1 <= N <= 30,000),Bessie正在研究的字符串的数量。

第2~N+1行:每行包含一个非空字符串。所有字符串包含的字符总数不会超过300,000。 输入中的所有字符都是小写字母,即a~z。 输入不包含重复的字符串。

输出格式:

第1行:一个数字K,表示按重排后的字母表顺序排列的字符串有多少可以排在第一个数量。

第2~K+1行:第i+1行包含第i个按重排后的字母表顺序排列后可以排在第一个的字符串。字符串应该按照它们在输入中的顺序来输出。

输入输出样例

4
omm
moo
mom
ommnom

**输出样例#1:

2
omm
mom

因为涉及到字典序的问题,那么应该能想到字典树。很显然字符串s1如果比字符串s2的字典序小的话,只有两种情况,s1是s2的前缀;以及他们有相同的前缀单在相同前缀后面的部分那一部分s1的优先级更大;

所以将所以字符串建成一棵字典树,然后再在字典树上遍历每一串字符串,如果有字符串已经是他的前缀,无论怎么改变26个字母的排列也无济于事,否则判断优先级,及上一段所说的情况二; 判断优先级有个很巧的办法---->

7304016-9b95975cf78b7b46.PNG
捕获.PNG

如图手画的十分丑陋不要介意哈

字符串s1 为 mma
字符串s2 为 mmb

现在要使mmb的优先级大于mma的优先级,那如何判断通过26个字母的变换后mmb能否优先于mma呢,应该一眼看出把a,b交换位置后s2的字典序就小于s1了,那么接下来就是要找到一种证明的方法;

他们都有相同的mm前缀,那么要使s2的优先级大,就要使b的优先级大,及相同前缀后面部分的优先级大,跟之前所说的一样。

设计到优先级的问题我们绞尽脑汁后应该能想到一个叫拓扑序的神奇的东西,那么再上一张丑图hh

7304016-80632cb0d3eb858d.PNG

构造这样的图,此时a,b在拓扑排序后的顺序是在同一层的,所以可以瞎把a,b交换位置都可以,故可以将a,b交换位置使得s2的优先级大于s1;

再来一个反例加深理解

7304016-306cab550301252b.PNG
捕获3.PNG

无法构成拓扑序,因为a,b无论怎么交换位置,mmab都在mmba前面

应该很清楚了吧!!

如果还不理解建议自己多画几个,一定要勇于动手!!

下面上代码喽!!

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<string>
#include<algorithm>
#include<queue>
#include<vector>
using namespace std;
struct Node{
    int son[27],end;
}N[300010];
vector<int> E[27];
int n,cnt=1,ind[30010],ans_sum,used[27][27];
string ans[30010],s[30010];
void insert(string s){
    int now=1;
    for(int i=0;i<s.length();i++){
        if(!N[now].son[s[i]-'a']) N[now].son[s[i]-'a']=++cnt;
        now=N[now].son[s[i]-'a'];
    }
    N[now].end++;
}
int work(string s){
    int now=1;
    for(int i=0;i<s.length();i++){
        int t=s[i]-'a';
        if(N[now].end) return 0;
        for(int j=0;j<26;j++){
            if(N[now].son[j]&&t!=j){
                E[t].push_back(j);
                ind[j]++;
            }
        }
        now=N[now].son[t];
    }
    return 1;
}
int check(){
    queue<int> q;
    for(int i=0;i<26;i++) if(!ind[i]) q.push(i);
    while(!q.empty()){
        int u=q.front(); q.pop();
        for(int i=0;i<E[u].size();i++){
            ind[E[u][i]]--;
            if(!ind[E[u][i]]) q.push(E[u][i]);
        }
    }
    for(int i=0;i<26;i++) if(ind[i]) return 0;
    return 1;
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        cin>>s[i];
        insert(s[i]);//建字典树 
    }
    for(int i=1;i<=n;i++){
        memset(used,0,sizeof(used));
        memset(ind,0,sizeof(ind));
        for(int j=0;j<27;j++) E[j].clear(); 
        if(!work(s[i])) continue;//如果有人是它的前缀返回0,直接不干了,如果没有,顺便建好图,准备接下来拓扑排序 
        if(check()) ans[++ans_sum]=s[i];//如果符合拓扑序 记录答案 
    }
    printf("%d\n",ans_sum);
    for(int i=1;i<=ans_sum;i++) cout<<ans[i]<<endl; 
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值