Newcoder 59 B.假的字符串(字典树+拓扑排序)

186 篇文章 0 订阅
6 篇文章 0 订阅

Description

给定 n n n个字符串,互不相等,你可以任意指定字符之间的大小关系(即重定义字典序),求有多少个串可能成为字典序最小的串,并输出它们

Input

第一行一个数表示 n n n

之后 n n n行每行一个字符串表示给定的字符串

( 1 ≤ n ≤ 3 ⋅ 1 0 4 , ∑ ∣ s ∣ ≤ 3 ⋅ 1 0 5 ) (1\le n\le 3\cdot 10^4,\sum|s|\le 3\cdot 10^5) (1n3104,s3105)

Output

第一行输出一个数 x x x表示可行的字符串个数

之后输出 x x x行,每行输出一个可行的字符串

输出的顺序和输入的顺序一致

Sample Input

6
mcfx
ak
ioi
wen
l
a

Sample Output

5
mcfx
ioi
wen
l
a

Solution

对所有串建字典树,如果一个串以另一个串为前缀,那么这个串必然不能是字典序最小的,否则在每个对应位置建立该串字符到其他串字符的偏序关系,以此建图,如果该图存在拓扑序则说明存在一种字符之间的大小关系使得该串字典序最小,否则不行,时间复杂度 O ( 26 ∑ ∣ s ∣ + 2 6 2 n ) O(26\sum|s|+26^2n) O(26s+262n)

Code

#include<cstdio>
#include<iostream>
#include<string>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;
const int maxn=300005;
string s[30005],ans[30005];
int tree[300005][27],val[300005],tot=0,n;
void insert(string s)
{
	int len=s.size(),p=0;
	for(int i=0;i<len;i++)
	{
		int x=s[i]-'a';
		if(tree[p][x]==0)tree[p][x]=++tot;
		p=tree[p][x];
	}
	val[p]=1;
}
int du[27];
vector<int>g[27];
bool top_sort()
{
	queue<int>que;
	int res=0;
	for(int i=0;i<26;i++)
		if(!du[i])que.push(i),res|=(1<<i);
	while(!que.empty())
	{
		int u=que.front();
		que.pop();
		for(int i=0;i<g[u].size();i++)
		{
			int v=g[u][i];
			du[v]--;
			if(!du[v])que.push(v),res|=(1<<v);
		}
	}
	return res==((1<<26)-1);
}
bool check(string s)
{
	int len=s.size(),p=0;
	memset(du,0,sizeof(du));
	for(int i=0;i<26;i++)g[i].clear();
	for(int i=0;i<len;i++)
	{
		int x=s[i]-'a';
		for(int y=0;y<26;y++)
			if(tree[p][y]&&y!=x)
			{
				du[y]++;
				g[x].push_back(y),g[y].push_back(x);
			}
		if(val[p])return 0;
		p=tree[p][x];
	}
	return top_sort();
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		cin>>s[i];
		insert(s[i]);
	}
	int res=0;
	for(int i=1;i<=n;i++)
		if(check(s[i]))ans[++res]=s[i];
	printf("%d\n",res);
	for(int i=1;i<=res;i++)cout<<ans[i]<<endl;
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值