官方题解错的,手动无视“深度”以及“dep”就对了。
一开始把前缀和后缀相同理解成了回文串,然后想到了用字典树+LCA,但是字典树太大了。。。然后就不会了。
如果我理解正确的话,应该是能很容易想到KMP+LCA的。
按照官方题解的说法,就是先对字符串求一下getfail,得到f[]数组,然后根据f[]数组对所有失配边各连一条边,就会形成一棵树。然后求两个前缀的最长公共前缀和最长公共后缀,就是找这两个前缀所对应节点的最近公共祖先,然后这个祖先节点所代表的前缀就是最长的公共前缀和后缀。输出这个前缀的长度(其实就是节点编号啦)即可。
为什么可以这样做呢?答案是KMP算法本质就是求各个前缀的最长前缀使得其等于这个前缀的等长的后缀。而这道题目不过是要求同时满足两个不同的前缀罢了。那么我们就可以不断地往失配边找,直到找到一个前缀能同时满足两个前缀的要求即可。如果用树建个模就是相当于求LCA啦。
代码
#include<bits/stdc++.h>
#define maxn 100010
using namespace std;
const int POW=18;
int d[maxn],p[maxn][POW];
vector<int>G[maxn];
void dfs(int u,int f)
{
d[u]=d[f]+1;
p[u][0]=f;
for(int i=1;i<POW;i++) p[u][i]=p[p[u][i-1]][i-1];
for(unsigned int i=0;i<G[u].size();i++)
{
int v=G[u][i];
if(v==f) continue;
dfs(v,u);
}
}
int lca(int a,int b)
{
if(d[a]>d[b]) swap(a,b);
int dist=d[b]-d[a];
for(int i=0;i<POW;i++) if(dist&(1<<i)) b=p[b][i];
if(a==b) return a;
else for(int i=POW-1;i>=0;i--)
if(p[a][i]!=p[b][i])
a=p[a][i],b=p[b][i];
return p[a][0];
}
char s[maxn];
int f[maxn];
int l;
void getfail()
{
f[0]=f[1]=0;
for(int i=1;i<l;i++)
{
int j=f[i];
while(j&&s[i]!=s[j]) j=f[j];
f[i+1]=s[i]==s[j]?j+1:0;
}
/*for(int i=0;i<=l;i++) printf("%d",f[i]);
puts("");*/
}
int kase;
void add(int u,int v)
{
G[u].push_back(v);
G[v].push_back(u);
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
scanf("%s",s);
l=strlen(s);
for(int i=1;i<=l+1;i++) G[i].clear();
getfail();
for(int i=1;i<=l;i++)
add(i+1,f[i]+1);
dfs(1,0);
int q,u,v;
scanf("%d",&q);
printf("Case #%d:\n",++kase);
while(q--)
{
scanf("%d %d",&u,&v);
u++,v++;
int w=lca(u,v);
if(w==u||w==v)
{
int ans=p[w][0]-1;
if(ans<0) ans=0;
printf("%d\n",ans);
}
else printf("%d\n",w-1);
}
}
return 0;
}