题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6138
题目意思:题目给出n个字符串后,会有m个询问,每个询问会输入两个数,记为x,y吧。
然后要求的是,x,y的最长公共字串长度,而且这个最长公共子串要是n个字符串中某个
字符串的前缀。
解题知识点是AC自动机。AC自动机原来是用来多模式匹配的,原来每个节点维护的值
有一个标记是否是一个完整的字符串,然而由于该题是要前缀长度,我们就要队节点维护
的值有所改进,每个节点维护其所代表的前缀的长度即可。
这样的话,我们可以用这n个字符串构建字典树,然后通过BFS找到每个节点对应的fail
指针,接下来,由于我们要找的是第x个字符串和第y个字符串的最长公共字串长度,且该字串
是n个字符串中某个字符串的前缀。因此我们需要用第x个字符串和第y个字符串在字典树上进行
一个匹配。当第x个字符串去匹配的时候,我们要做的就是把所有在匹配时经过的节点都标记一下。
代表这些节点到字典树的根所代表的字符串既是某个字符串的前缀,还在第x个字符串中。同理
我们让第y个字符串在按相同的方法跑一遍AC自动机,有所不同的是,第一个串跑完之后已经留下
标记,我们可以在第2个串跑的时候,直接通过检测标记来确定某个节点所代表的字符串是不是x,y
的共同子串。然后边检测边求解答案就可以了。
代码我是用数组实现AC自动机的,链表的害怕超时,并没有写。写数组的真费劲,感觉还是链表比较爽。
AC代码如下:
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <queue>
using namespace std;
const int allSon = 26;
const int maxn = 100010;
int node[maxn][allSon]; ///字典树节点
int fail[maxn]; ///fail指针
int len[maxn]; ///存放根到当前节点的字符串长度
int sign[maxn][2]; ///存放标记,sign[maxn][0]存放第一个串跑AC自动机的标记,sign[maxn][1]放第二个。
char patten[maxn]; ///存放文本串
int LCS; ///存放最长公共字串长度
int id; ///用来给节点编号
int len1[maxn]; ///存放第i个字符串在patten中的开始位置
int len2[maxn]; ///存在第i个字符串的长度
///将模式串插入到字典树
void insertPatten(int left,int right) ///左右边界
{
int p = 0; ///0就是根节点
int index = left;
while(index < right)
{
int lowercase = patten[index]-'a';
if(node[p][lowercase] == -1) ///对应的节点不存在,创建节点
{
memset(node[id],-1,sizeof(node[id])); ///儿子节点置空
len[id] = len[p]+1; ///该节点代表的前缀长度是其父节点加1
fail[id] = -1; ///失败指针不存在
node[p][lowercase] = id++; ///相应位置存放节点编号
}
p = node[p][lowercase];
index++;
}
}
void build_AC_automaton()
{
queue<int>qu;
int p = 0; ///指向根节点
qu.push(p);
while(!qu.empty())
{
p = qu.front();
qu.pop();
for(int i = 0; i < allSon; i++)
{
if(node[p][i] !=-1) ///这个孩子存在
{
if(p == 0) ///p是根节点,其fail指针指向根
{
fail[node[p][i]] = 0;
}
else
{
int temp = fail[p];
while(temp != -1)
{
if(node[temp][i] != -1)
{
fail[node[p][i]] = node[temp][i];
break;
}
temp = fail[temp];
}
if(temp == -1)
fail[node[p][i]] = 0;
}
qu.push(node[p][i]);
}
}
}
}
///在AC自动机中进行匹配
void find_in_AC_automaton(int left,int right,int flag)
{
int p = 0; ///根节点
int index = left;
LCS = -1;
while(index < right)
{
int lowercase = patten[index]-'a';
while(node[p][lowercase]==-1 && p!=0)
p = fail[p];
p = node[p][lowercase];
if(p == -1) p = 0;
int temp = p;
while(temp!=0 && sign[temp][flag] != flag)
{
sign[temp][flag] = flag;
if(flag == 1) ///flag=1代表第一个串已经跑过AC自动机了,现在是第2个串
{
///代表这个前缀是两个串的公共子串,直接在第2个串跑AC自动机的时候计算答案
if(sign[temp][0]!=-1 && sign[temp][1]!=-1)
{
LCS = max(LCS,len[temp]);
}
}
temp = fail[temp];
}
index++;
}
}
int main()
{
int t,n,m,x,y; ///测试数据组数
scanf("%d",&t);
while(t--)
{
scanf("%d",&n);
memset(node[0],-1,sizeof(node[0]));
len[0] = 0;
fail[0] = -1;
id = 1;
memset(len1,0,sizeof(len1)); ///len1[i]保存第i个串的开始位置
memset(len2,0,sizeof(len2)); ///len2[i]保存第i个串的长度。
///将所有字符串存到同一个数组中,节省空间,并通过这两个数组结合实现快速访问
int prelen = 0;
for(int i = 1; i <= n; i++)
{
scanf("%s",patten+prelen);
len2[i] = strlen(patten+prelen); ///第i串的串长
len1[i] = prelen;
insertPatten(prelen,prelen+len2[i]);
prelen += len2[i];
}
build_AC_automaton();
scanf("%d",&m);
while(m--)
{
scanf("%d%d",&x,&y);
memset(sign,-1,sizeof(sign));
///直接锁定所要查询的串在patten中的范围。
find_in_AC_automaton(len1[x],len1[x]+len2[x],0); ///跑两次AC自动机
find_in_AC_automaton(len1[y],len1[y]+len2[y],1);
printf("%d\n",LCS);
}
}
return 0;
}