BZOJ3012:trie树+拓扑排序

看到这题,最直接的想法就是枚举字典序(全排列),但26个字母就有26!种全排列,显然会超时。

考虑一个串字典序不可能最小的情况,有如下两点:

1、不能有另一个串是它的前缀,如ab的字典序永远大于abb

2、字母之间的大小关系不能自相矛盾,举个例子:abcabbacbc三个串中,abc的字典序不可能最小。因为如果abc的字典序最小,那么abc的字典序小于abb,即c的字典序小于b,且abc的字典序小于acbc,即b的字典序小于c(要使A串的字典序小于B串,应找到两串第一个不相同的字母A[i]B[i],然后令A[i]<B[i]),两者自相矛盾。可以发现,字母之间的大小关系自相矛盾,就是大小关系出现环状,如a<bb<cc<a。因此,我们可以考虑建图,每个字母作为一个结点。如何将大小关系在图中表示?要令A[i]<B[i],就在图中连一条从A[i]指向B[i]的有向边。至于判环,拓扑排序就能实现。

具体做法:先把所有字符串存进trie树,再对每一串进行判断,如果有另一个串是它的前缀,直接return,然后枚举判断当前遍历到的trie树的结点有无其他子结点(即为其他字符串与本串第一个不相同的字母),并在图中由本串的字母向其他串的字母连边(注意判重),表示小于关系,最后拓扑排序判环。

#include<cstdio>
#include<cstring>
#define maxa 30
#define maxn 30010
#define maxs 300010
#define r register
int n;
int cnt,son[maxs][maxa];//储存trie树
int tmp,now;//遍历trie树
int num,first[maxa],to[maxa*maxa],next[maxa*maxa];//邻接表
int head,tail,q[maxa],deg[maxa];//拓扑排序
int top,ans[maxn];//存答案
int len[maxn],start[maxn];//每个字符串在字符数组中的位置
bool added[maxa][maxa];//判断重边
bool vis[maxs];//判断trie树中的一个结点是否是其他字符串的末尾
char ch;
char str[maxs];//字符串总长不超过300000,用一个字符数组存下所有字符串
bool check(r int x)
{
	num=tmp=head=tail=0;
	memset(first,0,sizeof(first));
	memset(to,0,sizeof(to));
	memset(next,0,sizeof(next));
	memset(deg,0,sizeof(deg));
	memset(added,0,sizeof(added));
	for(r int i=start[x];i<start[x]+len[x];i++)
	{
		now=str[i]-96;
		if(vis[tmp]) return 0;//有另一个串是它的前缀
		for(r int j=1;j<=26;j++)//扫描其他字符串和这个串第一个不同的字母
			if(j!=now&&son[tmp][j]&&!added[now][j])
				added[now][j]=1,to[++num]=j,next[num]=first[now],first[now]=num,deg[j]++;
		tmp=son[tmp][now];
	}
	for(r int i=1;i<=26;i++)//拓扑排序
		if(!deg[i]) q[++tail]=i;
	while(tail>head)
	{
		tmp=q[++head];
		for(int i=first[tmp];i;i=next[i])
		{
			deg[to[i]]--;
			if(!deg[to[i]]) q[++tail]=to[i];
		}
	}
	for(r int i=1;i<=26;i++)
		if(deg[i]) return 0;
	return 1;
}
int main()
{
	scanf("%d",&n),getchar(),start[0]=1;
	for(r int i=1;i<=n;i++)
	{
		start[i]=start[i-1]+len[i-1],tmp=0;
		while(ch=getchar(),ch!='\n')
		{
			str[start[i]+len[i]++]=ch;
			if(!son[tmp][ch-96]) son[tmp][ch-96]=++cnt;
			tmp=son[tmp][ch-96];
		}
		vis[tmp]=1;
	}
	for(r int i=1;i<=n;i++)
		if(check(i)) ans[++top]=i;
	printf("%d\n",top);
	for(r int i=1;i<=top;i++)
	{
		for(r int j=start[ans[i]];j<start[ans[i]]+len[ans[i]];j++) printf("%c",str[j]);
		printf("\n");
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/pig_dog_baby/article/details/81141333
今日推荐