题目描述:
给定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++