题意:
给定\(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;
}