[BZOJ1195]:[HNOI2006]最短母串(AC自动机+BFS)

题目传送门


题目描述:

给定n个字符串(S1,S2,…,Sn),要求找到一个最短的字符串T,使得这n个字符串(S1,S2,…,Sn)都是T的子串。


输入格式:

第一行是一个正整数n,表示给定的字符串的个数。 以下的n行,每行有一个全由大写字母组成的字符串。每个字符串的长度不超过50。
输出格式: 只有一行,为找到的最短的字符串T。在保证最短的前提下, 如果有多个字符串都满足要求,那么必须输出按字典序排列的第一个。
样例: 样例输入: 2
ABCD
BCDABC 样例输出: ABCDABC
数据范围与提示: 对于全部数据,1≤n≤12,1≤|Si|≤50。
题解:

一看是多模式串,首先应该想到是AC自动机。

如果还不会AC自动机,可以转到这篇博客,个人感觉还是写的挺清楚的:AC自动机讲解+[HDU2222]:Keywords Search(AC自动机)

那么我们考虑怎么去处理。

看到n的取值范围之后,我们可以考虑状态压缩。 然后,要求串最短,且字典序最小,那么可以考虑BFS从跟节点开始,每个状态A->Z爆搜,那么这样的话就可以保证当前搜到的串。 统计答案时从最后一个点往前找即可。
代码时刻:
#include<bits/stdc++.h>
using namespace std;
int trie[5000][5000],cnt=1,ed[5000],nxt[5000];
char s[50];
int que[5000],que1[10000000],que2[10000000],que3[10000000],que4[10000000];
//que用于求Fail指针
//que1-4用于BFS,que1用于存储答案,que2用于存储上一个点在队列中的位置,que3用与存储当前点的编号,que4用于存储状态
bool vis[5000][5000];
int tot;
int n;
int ans[5000];
void insert(char *str,int id)//依然建树
{
	int len=strlen(str);
	int p=1;
	for(int i=0;i<len;i++)
	{
		int ch=str[i]-'A';
		if(!trie[p][ch])trie[p][ch]=++cnt;
		p=trie[p][ch];
	}
	ed[p]|=1<<(id-1);
}
void build()//找Fail指针
{
	for(int i=0;i<26;i++)trie[0][i]=1;
	que[1]=1;
	int head=1,tail=1;
	while(head<=tail)
	{
		for(int i=0;i<26;i++)
		{
			if(!trie[que[head]][i])trie[que[head]][i]=trie[nxt[que[head]]][i];
			else
			{
				que[++tail]=trie[que[head]][i];
				nxt[trie[que[head]][i]]=trie[nxt[que[head]]][i];
				ed[trie[que[head]][i]]|=ed[trie[nxt[que[head]]][i]];
			}
		}
		head++;
	}
}
void ask()//BFS
{
	int head=1,tail=1;
	int end=(1<<n)-1;
	que3[1]=1;
	while(head<=tail)
	{
		if(que4[head]==end)
		{
			while(head>1)
			{
				ans[++tot]=que1[head];
				head=que2[head];
			}
			while(tot--)printf("%c",ans[tot+1]+'A');
			return;
		}
		for(int i=0;i<26;i++)
			if(!vis[trie[que3[head]][i]][que4[head]|ed[trie[que3[head]][i]]])//用vis存储当前状态有没有被访问过
			{
				que1[++tail]=i;//添加这个点
				que2[tail]=head;//存储上一个点
				que3[tail]=trie[que3[head]][i];//存储当前点的编号
				que4[tail]=que4[head]|ed[trie[que3[head]][i]];//存储状态
				vis[trie[que3[head]][i]][que4[head]|ed[trie[que3[head]][i]]]=1;//表示当前状态已经被访问过
			}
		head++;
	}
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%s",s);
		insert(s,i);
	}
	build();
	ask();
	return 0;
}

rp++

上一篇:IBM MQ扩大队列最大消息长度


下一篇:为什么对1取反为-2?