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;
}
posted @ 2022-07-08 21:08  Jerry_Black  阅读(51)  评论(0)    收藏  举报