牛客15049 假的字符串(字典树+拓扑排序)

题意:

给定n个字符串,互不相等,你可以任意指定字符之间的大小关系(即重定义字典序),求有多少个串可能成为字典序最小的串,并输出它们。

题解:

首先,当另一个字符串是当前字符串的前缀时,这个字符串一定不可能是最小的,这一点可以用字典树实现。

然后,可能出现矛盾的情况,使得当前字符串不可能是最小,这里先在字典树上扫描一遍字符串,将每个节点和它的兄弟萌连一条单向边,这样可以形成一个有向图,对这个有向图做拓扑排序,如果有环,就不合法,否则合法。

用拓扑排序判环的方法就是查看入队过的节点数是否等于整个有向图的节点数。

#include<bits/stdc++.h>
using namespace std;
const int maxn=3e5+10;
typedef long long ll;
vector<string> ans;
string s[maxn];
int Trie[maxn][27];
int cnt[maxn];
int inDegree[27];
int tot=0,n;
vector<int> g[27];
void insert (string s) {
    int root=0;
    for (int i=0;i<s.length();i++) {
        int tt=s[i]-'a';
        if (!Trie[root][tt]) Trie[root][tt]=++tot;
        root=Trie[root][tt];
    }
    cnt[root]++;
}
queue<int> q;
bool topo () {
    ll f=0;
    for (int i=0;i<26;i++) if (!inDegree[i]) q.push(i),f|=(1<<i);
    while (!q.empty()) {
        int u=q.front();
        q.pop();
        for (int i=0;i<g[u].size();i++) {
            int v=g[u][i];
            if (--inDegree[v]==0) q.push(v),f|=(1<<v);
        }
    }
    return f==((1<<26)-1);
}
bool check (string s) {
    memset(inDegree,0,sizeof(inDegree));
    int root=0;
    for (int i=0;i<26;i++) g[i].clear();
    for (int i=0;i<s.length();i++) {
        for (int j=0;j<26;j++) {
            int tt=s[i]-'a';
            if (Trie[root][j]&&j!=tt) 
                inDegree[j]++,g[tt].push_back(j);
        } 
        if (cnt[root]) return false;
        root=Trie[root][s[i]-'a'];
    }
    return topo();
}
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++) if (check(s[i])) ans.push_back(s[i]);
    printf("%d\n",ans.size());
    for (int i=0;i<ans.size();i++) cout<<ans[i]<<'\n';
    return 0;
}

 

posted @ 2020-06-30 20:40  zlc0405  阅读(213)  评论(0编辑  收藏  举报