牛客练习赛11 B-假的字符串(trie字典树+topo判环)

题目

给定一个n(n<=3e4),以下n个均为小写字母的字符串,互不相等,你可以任意指定字符之间的大小关系(即重定义字典序),

求有多少个串可能成为字典序最小的串,并输出它们,字符串总长≤3e5

思路来源

某篇 2015年的集训队论文 例题

题解

①如果A串是B串的前缀,则B串一定不是字典序最小的

②否则,判断B串的每一位y,在每一位的同一层的字母x只能字典序比y大

y->所有x连边,建图,拓扑判断是否有环,无环则说明可行,否则不可行

由于边实际上可能较多,为了防若干不必要重边,还是建邻接矩阵吧

复杂度O(26*字符串总长+26*26*n)

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=3e4+10,M=3e5+10;
string s[N];
int tr[M][26],num[M],cnt;
int n,rt,res;
int in[26];
bool ok[N],mp[26][26];
queue<int>q;
bool vis[26];
bool topo()
{
	for(int i=0;i<26;++i)
	{
		for(int j=0;j<26;++j)
		{
			if(mp[i][j])in[j]++;
		}
	}
	for(int i=0;i<26;++i)
	{
		if(in[i]==0)
		{
			q.push(i);
			vis[i]=1; 
		} 
	}
	while(!q.empty())
	{
		int t=q.front(); 
		q.pop();
		for(int j=0;j<26;++j)
		{
			if(!mp[t][j])continue;
			mp[t][j]=0;
			if((--in[j])==0)
			{
				q.push(j);
				vis[j]=1;
			}
		}
	}
	for(int i=0;i<26;++i)
	if(!vis[i])return 1;
	return 0;
}
void add(int rt,string &s,int id)
{
	int len=s.size();
	for(int i=0;i<len;++i)
	{
		int v=s[i]-'a';
		if(!tr[rt][v])
		{
			tr[rt][v]=++cnt;
			num[cnt]=0;
			memset(tr[cnt],0,sizeof tr[cnt]);
		}
		rt=tr[rt][v];
	}
	num[rt]=id;
}
bool solve(int id)
{
	for(int i=0;i<26;++i)
	{
		in[i]=vis[i]=0;
		for(int j=0;j<26;++j)
		mp[i][j]=0;
	}
	int rt=0;
	int len=s[id].size();
	for(int i=0;i<len;++i)
	{
		int v=s[id][i]-'a';
		if(num[rt])return 0;
		for(int j=0;j<26;++j)
		{
			if(j==v||!tr[rt][j])continue;
			mp[v][j]=1;
		}
		rt=tr[rt][v];
	}
	if(topo())return 0;
	return 1;
}
int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	cin>>n;
	rt=cnt=0;
	num[0]=0;
	memset(tr[0],0,sizeof tr[0]);
	for(int i=1;i<=n;++i)
	{
		cin>>s[i];
		add(rt,s[i],i);
	}
	for(int i=1;i<=n;++i)
	if(solve(i))ok[i]=1,res++;
	cout<<res<<endl;
	for(int i=1;i<=n;++i)
	if(ok[i])cout<<s[i]<<endl;
	return 0;
}
发布了467 篇原创文章 · 获赞 53 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/Code92007/article/details/102922842