First!(ZJNU 2832)
题目大意
你有\(n\)个字符串,现在你可以改变字典序的规则(改变字母间大小关系),使得某字符串成为字典序最小的字符串,问字符串\(S_1,S_2,\cdots,S_n\)中哪些可以成为字典序最小的字符串。\((1\le n\le30000)\)
思路
由于我们想让某字符串字典序最小,那么就一定能得到字母与字母间的大小关系,而且如果这些关系没有冲突,那么就说明是能满足字典序最小的。对于这样的关系,我们可以用单向边来表示,然后冲突就说明有环,可以用拓扑或\(DFS\)来处理。但是由于\(n\)的范围,我们不可能对所有的字符串两两比较,于是我们考虑用\(trie\)树。我们可以先把所有的字符串都插到\(trie\)树中,然后再拿每一个字符串放到\(trie\)树中比较,就能很快得到字母间的关系,然后通过拓扑就能轻松解决。
代码
#include<bits/stdc++.h>
using namespace std;
int trie[500005][30];
int ed[500005];
int k=1;
int edge[30][30];
void insert(string w)
{
int len=(int)w.size();
int p=0;
for(int i=0;i<len;i++)
{
int c=w[i]-'a';
if(!trie[p][c])
trie[p][c]=k++;
p=trie[p][c];
}
ed[p]=1;
}
int search(string s)
{
int len=(int)s.size();
int p=0;
int in[30]={0};
int cc=26;
for(int i=0;i<len;i++)
{
if(ed[p])return 0;
int c=s[i]-'a';
for(int j=0;j<26;j++)if(j!=c&&trie[p][j])
{
if(!edge[c][j])in[j]++;
edge[c][j]=1;
}
p=trie[p][c];
}
queue<int>q;
for(int i=0;i<26;i++)if(in[i]==0)q.push(i);
while(!q.empty())
{
int d=q.front();
cc--;
q.pop();
for(int i=0;i<26;i++)
{
if(edge[d][i])
{
in[i]--;
if(in[i]==0)q.push(i);
}
}
}
if(cc)return 0;
return 1;
}
string s[50005];
int cnt=0;
int vis[50005];
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
cin>>s[i];
insert(s[i]);
}
for(int i=1;i<=n;i++)
{
for(int j=0;j<26;j++)
for(int kd=0;kd<26;kd++)
edge[j][kd]=0;
vis[i]=search(s[i]);
if(vis[i])cnt++;
}
printf("%d\n",cnt);
for(int i=1;i<=n;i++)
if(vis[i])cout<<s[i]<<endl;
return 0;
}

浙公网安备 33010602011771号