题意:
给你两个串,问你他们的最长公共子串的长度为多少
题解:
后缀自动机,大致细节我都写在注释里面了
还算可以的时间复杂度
#include<iostream>
#include<stdio.h>
#include<cstring>
using namespace std;
#define ll long long
const int N = 2e5+1000;
char s[N];
int k,lens;
struct SAM{
int last,cnt,nxt[N*2][26],fa[N*2],l[N*2],num[N*2];
int lasrt;//表示当前字符串所匹配的字典树的位置
void init(){
last = cnt=1;
memset(nxt[1],0,sizeof nxt[1]);
fa[1]=l[1]=num[1]=0;
lasrt=1;
}
int inline newnode(){
cnt++;
memset(nxt[cnt],0,sizeof nxt[cnt]);
fa[cnt]=l[cnt]=num[cnt]=0;
return cnt;
}
void add(int c){
int p = last;
int np = newnode();
last = np;
l[np] =l[p]+1;
while (p&&!nxt[p][c]){
nxt[p][c] = np;
p = fa[p];
}
if (!p){
fa[np] =1;
}
else{
int q = nxt[p][c];
if (l[q]==l[p]+1){
fa[np] =q;
}
else{
int nq = newnode();
memcpy(nxt[nq],nxt[q],sizeof nxt[q]);
fa[nq] =fa[q];
l[nq] = l[p]+1;
fa[np] =fa[q] =nq;
while (nxt[p][c]==q){
nxt[p][c]=nq;
p = fa[p];
}
}
}
int tmp=last;
while(tmp&&!num[tmp])
num[tmp]|=1,tmp=fa[tmp];
/*
如果这个点之前已经被访问过了,就说明它以及所有父类都被访问过
那么直接停止
否则就是n*n的时间复杂度
*/
}
void back_id(int len,int bin)//当前面有一个字符被删掉的时候跳回去
{
while(lasrt&&l[fa[lasrt]]>=len)
lasrt=fa[lasrt];
/*
因为l[fa[a]]<l[a],所以可以用这个性质跳到父类.
*/
lasrt=lasrt?lasrt:1;
//这里需要判断一下因为会len会<=0
num[lasrt]|=(1<<bin);//用二进制就可以做最大范围是63的最长公共子串
}
void add_id(int len,int c,int bin)//新进来字符时跳到下一个点
{
lasrt=nxt[lasrt][c];
//因为已经必然有这个方向的字典树了,所以不需要判断是否为0
back_id(len,bin);
}
int is_substr(int nexc)
{
return nxt[lasrt][nexc];
}
}sam;
char ss[N];
ll dp[N];
int main()
{
//scanf("%d",&k);
scanf("%s",s);
int len=strlen(s);
sam.init();
for(int i=0;i<len;i++)
sam.add(s[i]-'a');
int mx=0;
sam.lasrt=1;
scanf("%s",s);
len=strlen(s);
int r=0;
for(int j=0;j<len;j++)
{
while(r<=j&&!sam.is_substr(s[j]-'a'))
{
r++;
sam.back_id(j-r,1);
//由于最前面的字符被删掉了,所以需要跳到父亲类中,为了查看接下来是否可以重新连边
//为什么是i-r是因为要跳到父类中呀,i-r+1就可能跳到子类中了
}
sam.add_id(j-r+1,s[j]-'a',1);
//如果跳到了根或者已经有到i的后缀了,那么就更新自动机中的尾指针
if(sam.num[sam.lasrt]==3&&j-r+1>mx)
mx=j-r+1;
}
printf("%d\n",mx);
return 0;
}