GDOI2016模拟8.16第一字符串

题目
Bessie最近在玩字符串。她发现通过改变字母的顺序可以使一些字符串排在其他所有的字符串之前(按字典序从小到大排序)。

比如说,Bessie找到了对于字符串”omm”,”moo”,”mom”,和”ommnom”,她可以用标准的字母表把”mom”排在最前面,也可以用字母表”abcdefghijklonmpqrstuvwxyz”把”omm”排在最前面。但是,Bessie不能找出一个方案使”moo”或”ommnom”排在最前面。

请你帮助Bessie找出哪些字符串可以通过改变字母表来让它们排在最前面。要计算字符串X是否字典序比字符串Y小,要找到两个字符串最先出现的不同字符的位置,j。如果没有这样的位置存在,那么如果X的长度比Y短,则X字典序小于Y。否则,如果X[j]在字母表中比Y[j]早出现,那么X字典序小于Y。

这道题,比较容易转化模型,对每个字符串进行判断,找出该字符串与其他字符串出现第一个不同的字母是哪个,然后连一条单向边,表示字母s1必须大于s2,这样得出了一个有向图,若图中有环,就不合法,否则合法(当然,要特判前面都相同,一个是另一个的前缀的情况)。

判环比较简单,可以用spfa、dfs打标记、拓扑排序、并查集….

关键在于如何快速查找每个字符串对该字符串第一个不同的字母,这个暴力会超时。
换个角度想,我们会发现这有点像多串匹配,而且匹配过程中有很多重复的地方,这时候我们可以用trie来进行优化,一方面,trie将匹配时很多重复前缀过滤了,另一方面,通过在树上打结束标记,我们可以在边往下走时知道到,该字符串是不是含有前缀是匹配串,用起来比较方便,具体可以参考代码,也不难想到。

贴代码

#include<iostream>
#include<cstdio>
#include<algorithm>
#define N 30002
#include<cstring>
#include<cmath>
using namespace std;
char c[N*10],s[N*10];
int n,st,len;
int a[N],in[100],ans[N],d[100],g[N*10][26];
bool f[100][100],bz[N],end[N*10];
void ins(int x,int y){
    static int z,sum=1;
    if (y>len){
        end[x]=1;
        return;
    }
    if (!g[x][z=s[st+y]-'a'])
        g[x][z]=++sum;
    ins(g[x][z],y+1);
}
void init(){
    scanf("%d",&n);
    for (int i=1;i<=n;i++){
        bz[i]=1;
        scanf(" %s",&c);
        a[i]=a[i-1]+(len=strlen(c));
        for (int j=0;j<len;j++)
            s[j+a[i-1]+1]=c[j];
        st=a[i-1];
        ins(1,1);
    }
}
void clear(){
    for (int i=0;i<26;i++){
        for (int j=0;j<26;j++)
            f[i][j]=0;
        in[i]=0;
    }
}
bool jian(){
    static int l,r;
    for (int i=0;i<26;i++)
        for (int j=0;j<=26;j++)
            in[j]+=f[i][j];
    l=r=0;
    for (int i=0;i<26;i++)
        if (!in[i])
            d[++r]=i;
    while (l<r){
        l++;
        for (int i=0;i<26;i++)
            if (f[d[l]][i]){
                --in[i];
                if (!in[i])
                    d[++r]=i;
            }
    }
    return r==26;
}
bool get(int x,int y){
    if (y>len)return 1;
    if (end[x])return 0;
    static int z;
    z=s[st+y]-'a';
    for (int i=0;i<26;i++)
        if (i!=z&&g[x][i])
            f[z][i]=1;
    return get(g[x][z],y+1);
}
void work(){
    static bool p;
    for (int i=1;i<=n;i++){
        clear();
        st=a[i-1],len=a[i]-a[i-1];
        p=get(1,1);
        if (p&&jian())
            ans[++ans[0]]=i;
    }
}
void write(){
    printf("%d\n",ans[0]);
    for (int i=1;i<=ans[0];i++){
        for (int j=a[ans[i]-1]+1;j<=a[ans[i]];j++)
            printf("%c",s[j]);
        printf("\n");
    }
}
int main(){
    init();
    work();
    write();
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值