问题提出
给定你一个字符串,有\(n\) 次询问,每次给出两个数\(a\),\(b\),希望求出该字符串以\(a\),\(b\)下标结尾的两段前缀的最长公共\(border\)。(一个字符串的\(border\)同时为其后缀与前缀)
初步分析
首先我们需要考虑一个字符串的最长\(border\)求法,毕竟需要求的是两字符串的最长公共\(border\)。从\(border\)的定义下手,我们可以发现我们需要将该字符串的后缀与其前缀进行匹配,所以考虑其与字符串匹配的关系。
再仔细分析,我们发现待求\(border\)字符串是给定字符串的一段前缀,所以其最长\(border\)一定也是给定字符串的一段前缀,且该\(border\)处于前面的结束位置是最后一个点在进行字符串匹配时的最后一个失配位置,(因为\(border\)应是一段后缀,其结尾必定是待求字符串的结尾)。
经过上述分析,我们很自然的会想到\(KMP\)算法中的\(fail\)数组,它正好记录了给定字符串中每一个字母的最后一个失配位置,(不会\(KMP\)的请戳这里),这部分代码如下:
for(int i = 2 , j = 0 ; i <= len ; ++i) //这里字符串下标默认从1开始
{
while(str[i] != str[j + 1] && j != 0)
j = fail[j] ;
if(str[i] == str[j + 1])
++j , fail[i] = j ;
}
进入正题
知道一段前缀的最长\(border\)怎么求后,我们再考虑如何求出两段前缀的最长公共\(border\)。
考虑一段字符串的\(border\)的形式,我们会发现其实每一次向前跳的过程都是相当于在向其父节点跳跃,形成一个树形的结构,形式化地再来解释两段前缀的最长公共\(border\)的前缀的结尾点就是它们在这个\(fail\)树上的最近公共祖先表示的点,于是我们考虑在进行\(KMP\)匹配是顺便将这棵\(fail\)树建出,(我们此处采用倍增\(lca\)写法)(不会\(lca\)的请戳这里),代码如下:
\(KMP\)+建树部分:
for(int i = 2 , j = 0 ; i <= len ; ++i)
{
while(str[i] != str[j + 1] && j != 0)
j = fail[j] ;
if(str[i] == str[j + 1])
++j , fail[i] = j , dep[i] = dep[j] + 1 ;
else
fail[i] = 0 , dep[i] = 0 ;
fa[i][0] = fail[i] ;
for(int k = 1 ; k <= 20 ; ++k)
fa[i][k] = fa[fa[i][k - 1]][k - 1] ;
}
倍增\(lca\)部分:
int lca(int u , int v)
{
int temp = 0 ;
if(dep[u] < dep[v])
swap(u , v) ;
temp = dep[u] - dep[v] ;
for(int i = 0 ; (1 << i) <= temp ; ++i)
if((1 << i) & temp)
u = fa[u][i] ;
if(u == v)
return 0 ;
for(int i = 20 ; i >= 0 ; --i)
if(fa[u][i] != fa[v][i])
u = fa[u][i] , v=fa[v][i] ;
return fa[u][0] ;
}