P3065 [USACO12DEC]First! G TRIE樹+拓撲排序
阿新 • • 發佈:2020-09-16
題意:
給定\(n\)個單詞,求在自定義字典序的情況下,多少個單詞的字典序最小,並按順序輸出
範圍&性質:\(1\le n\le 3\times 10^4\),每個單詞長度不超過20
分析:
暴力做法:
可知,對於一個固定的字典序,有且僅有一個單詞字典序最小(不考慮完全相同),所以樸素的做法有了,列舉字典序,在TRIE樹上找出對應的字串,複雜度\(O(26!\times20)\)(字典序數目\(\times\)TRIE樹深度)
正解:
假定每一個單詞都可以作為最小字典序,對於每個單詞按照形成的字典序(上面提到字典序和單詞一一對應),由字典序小的字母向大的字母連邊,若存在環則假設不成立。複雜度為\(O(26*nm)\)
tips:
- 判環可以利用拓撲排序,最後若存在度數不為0的點,即存在環
- 對於字首相同的字串,大的字典序不可能最小,直接判斷就好
程式碼:
#include<bits/stdc++.h> using namespace std; namespace zzc { const int maxn = 3e5+5; int n,cnt=1,sum=0; int son[maxn][26],rd[30],ans[maxn]; char ch[maxn][30]; bool vis[maxn*10]; struct edge { int to,nxt; }e[905]; int ecnt=0; int head[30]; void add(int u,int v) { e[++ecnt].to=v; e[ecnt].nxt=head[u]; rd[v]++; head[u]=ecnt; } void insert(int x) { int len=strlen(ch[x]+1),now=1; for(int i=1;i<=len;i++) { int tmp=ch[x][i]-'a'; if(!son[now][tmp]) son[now][tmp]=++cnt; now=son[now][tmp]; } vis[now]=true; } bool check(int x) { int len=strlen(ch[x]+1),now=1; for(int i=1;i<=len;i++) { int tmp=ch[x][i]-'a'; if(vis[now]) return false; for(int i=0;i<26;i++) { if(tmp!=i&&son[now][i]) { add(tmp,i); } } now=son[now][tmp]; } return true; } bool topo() { queue<int> q; for(int i=0;i<26;i++) { if(!rd[i]) { q.push(i); } } while(!q.empty()) { int u=q.front();q.pop(); for(int i=head[u];i;i=e[i].nxt) { int v=e[i].to; if(rd[v]) { if(--rd[v]==0) { q.push(v); } } } } for(int i=0;i<26;i++) { if(rd[i]) return false; } return true; } void work() { scanf("%d",&n); for(int i=1;i<=n;i++) { scanf("%s",ch[i]+1); insert(i); } for(int i=1;i<=n;i++) { memset(rd,0,sizeof(rd)); memset(head,0,sizeof(head)); ecnt=0; if(check(i)&&topo()) { ans[++sum]=i; } } printf("%d\n",sum); for(int i=1;i<=sum;i++) { printf("%s\n",ch[ans[i]]+1); } } } int main() { zzc::work(); return 0; }