BZOJ_3012_[Usaco2012 Dec]First!_trie树+拓扑排序

BZOJ_3012_[Usaco2012 Dec]First!_trie树+拓扑排序

题意:

给定n个总长不超过m的互不相同的字符串,现在你可以任意指定字符之间的大小关系。问有多少个串可能成为字典序最小的串,并输出这些串。n <= 30,000 , m <= 300,000
分析:
首先不考虑大小关系,如果一个串是另一个串的前缀,那么另一个串一定不能成为字典序最小的串,我们可以用trie树很好的解决。
考虑前缀相同的情况,这个串在前缀后的字符应该和含有相同前缀的串在前缀后的字符有明确的大小关系,根据这个大小关系连边,我们用拓扑排序判断是否矛盾。
以上都满足则可以成为字典序最小的串。
代码:
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <queue>
using namespace std;
#define N 30050
struct A
{
    int son[30],end;
}t[N*10];
int n,tot,head[30],to[N],nxt[N],c[30],cnt=1,ans[30010];
char s[30010][310];
void add(int u,int v)
{
    to[++cnt]=v;
    nxt[cnt]=head[u];
    head[u]=cnt;
    c[v]++;
}
void insert(int x)
{
    int p=1;
    int len=strlen(s[x]+1);
    for(int i=1;i<=len;i++)
    {
        int id=s[x][i]-'a'+1;
        if(!t[p].son[id])t[p].son[id]=++cnt;
        p=t[p].son[id];
    }
    t[p].end=1;
}
bool search(int x)
{
    int p=1;
    int len=strlen(s[x]+1);
    for(int i=1;i<=len;i++)
    {
        if(t[p].end)return 0;
        int id=s[x][i]-'a'+1;
        for(int j=1;j<=26;j++)
        {
            if(j!=id&&t[p].son[j])
            {
                add(id,j);
            }
        }
        p=t[p].son[id];
    }
    return 1;
}
bool topsort()
{
    queue <int> q;
    for(int i=1;i<=26;i++)if(c[i]==0)q.push(i);
    while(!q.empty())
    {
        int x=q.front();q.pop();
        for(int i=head[x];i;i=nxt[i])
        {
            c[to[i]]--;
            if(c[to[i]]==0)q.push(to[i]);
        }
    }
    for(int i=1;i<=26;i++)if(c[i])return 0;
    return 1;
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%s",s[i]+1);
        insert(i);
    }
    for(int i=1;i<=n;i++)
    {
        memset(head,0,sizeof(head));
        memset(c,0,sizeof(c));cnt=0;
        if(!search(i))continue;
        if(!topsort())continue;
        ans[++tot]=i;
    }
    printf("%d\n",tot);
    for(int i=1;i<=tot;i++)
    {
        printf("%s\n",s[ans[i]]+1);
    }
}

 

 

 

转载于:https://www.cnblogs.com/suika/p/8435786.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值