P3065 [USACO12DEC]First! G

Trie树 上拓扑。

为什么这道题目要建立 Trie树 呢...因为对于两个字符串,比较他们的字典序大小无非就是比较他们第一个相当的字符是什么,也就是说,如果这两个字符串在第 \(k\) 位开始不一样,那么他们也会在 \(k\) 对应的节点分别走向 \(k\) 不同的儿子。

暴力的思路无非就是暴力枚举、暴力判断吧...枚举应该是不能优化了(?),而判断可以借助 Trie树 来优化判断呀。存在大小关系...是不是可以将大小关系变为一个有向图呢?如果这个字符不可能是解的话,一定存在一组字符 \(u\) 和 \(v\) 使得 \(u\) 既要优先于 \(v\) ,\(v\) 也要优先与 \(u\)...

对于一个字符串,我们先假定这个字符串是字典序最小的,那么是不是就可以确定每个字符之间的大小关系呢...所以我们可以对于这个钦定的字符串 \(s\) 的每一位对应的 Trie树 上的节点 \(id\) ,遍历 \(id\) 的每一个子结点(因为 Trie树 上并没有记录每个点存在的子结点有几个,所以只能 \(0\) 至 \(25\) 来枚举),如果这个子结点有不同于 \(s\) 这一位的字符 \(j\) ,并且 \(id\) 存在 \(j\) 这个儿子,并且 \(tmp\) 暂时没有与 \(j\) 确定大小关系,那么很明显 \(tmp\) 是要优先于 \(j\) 的 ,因此 \(tmp\) 向 \(j\) 连上一条边。

很显然,如果存在一个可以的排列的话,这个有向图一定是一个 DAG !每个字符都存在一个固定字符排在它的前一位...

于是通过这个循环,我们确定了优先级...

ll id=0,len=x.length();
    memset(e,0,sizeof(e));
    memset(in,0,sizeof(in));
    for(register int i=0;i<len;++i){
        if(ed[id])  return 0;
        ll tmp=x[i]-'a';
        for(register int j=0;j<26;++j)
            if(tmp!=j&&trie[id].ch[j]&&!e[tmp][j]){
                e[tmp][j]=1;
                ++in[j];
            }
        id=trie[id].ch[tmp];
    }

如何判断是不是 DAG 呢...拓扑排序啊。

如果一个点始终不能将入度变为 \(0\) ,说明这个有向图是存在环的!这样就很好判断了呀~如果存在环说明一定存在一个 \(u\) 和一个 \(v\) 使得 \(u\) 要优先于 \(v\) 而 \(v\) 也要优先于 \(u\)!

至此,这道题目就做完了呀...

拓扑排序:

queue<ll>q;
    while(!q.empty())    q.pop();
    for(register int i=0;i<26;++i)  
        if(!in[i])  
            q.push(i);
    while(!q.empty()){
        ll u=q.front();q.pop();
        for(register int i=0;i<26;++i)
            if(e[u][i]){
                --in[i];
                if(!in[i])
                    q.push(i);
            }
    }

判断是否为 DAG:

for(register int i=0;i<26;++i)
        if(in[i])
            return 0;
    return 1;

完整程序:

#include <bits/stdc++.h>
#define ll long long
using namespace std;
ll n,cnt,node,ed[400001],in[30],ans,e[30][30],ok[30001];
string s[30001],c;
struct Node{
    ll fail=0,num=0,ch[25];
}trie[400001];

inline ll read(){
    ll x=0,f=0;char c=getchar();
    while(!isdigit(c))  f|=c=='-',c=getchar();
    while(isdigit(c))   x=(x<<1)+(x<<3)+(c^48),c=getchar();
    return f?-x:x;
}

inline void build(ll x){
    ll id=0,len=s[x].length();
    for(register int i=0;i<len;++i){
        ll tmp=s[x][i]-'a';
        if(!trie[id].ch[tmp])
            trie[id].ch[tmp]=++cnt;
        id=trie[id].ch[tmp];
    }
    ed[id]=1;
}

inline int find(string x){
    ll id=0,len=x.length();
    memset(e,0,sizeof(e));
    memset(in,0,sizeof(in));
    for(register int i=0;i<len;++i){
        if(ed[id])  return 0;
        ll tmp=x[i]-'a';
        for(register int j=0;j<26;++j)
            if(tmp!=j&&trie[id].ch[j]&&!e[tmp][j]){
                e[tmp][j]=1;
                ++in[j];
            }
        id=trie[id].ch[tmp];
    }
    queue<ll>q;
    while(!q.empty())    q.pop();
    for(register int i=0;i<26;++i)  
        if(!in[i])  
            q.push(i);
    while(!q.empty()){
        ll u=q.front();q.pop();
        for(register int i=0;i<26;++i)
            if(e[u][i]){
                --in[i];
                if(!in[i])
                    q.push(i);
            }
    }
    for(register int i=0;i<26;++i)
        if(in[i])
            return 0;
    return 1;
}

int main(){
    n=read();
    for(register int i=1;i<=n;++i){
        cin>>s[i];
        build(i);
    }
    for(register int i=1;i<=n;++i)
        if(find(s[i])){
            ++ans;
            ok[i]=1;
        }
    printf("%lld\n",ans);
    for(register int i=1;i<=n;++i)
        if(ok[i])
            cout<<s[i]<<endl;
    return 0;
}
上一篇:我使用的oracle语句


下一篇:NOIP 模拟 $26\; \rm 神炎皇$