题目链接:https://www.spoj.com/problems/LCS/en/
解题思路:
数据量和时间限制摆明了准备卡掉O(N*logN)的求法,后缀自动机可以O(N)解决
讲解后缀自动机的博客(易懂):https://www.luogu.org/blog/Kesdiael3/hou-zhui-zi-dong-ji-yang-xie
讲解后缀自动机的PPT(高深):https://wenku.baidu.com/view/fa02d3fff111f18582d05a81.html
当时对着博客魔改出一个自己的板子结果每一个改的地方全部出现了问题:
①根节点想要强行用0(博客中用1),需要令fa[0] = -1,然后判断接下去没有节点跳的条件就变成了:p!=-1,否则跳到p=0停下来你不知道是找到了还是没找到。
②用于记录上一轮前缀(整个串)属于哪一个节点的las不能去掉改为用sz-1,因为你不知道上一次添加是多了一个节点还是两个。当前你可以记录每轮产生的是一个节点还是两个,产生一个就是sz-1,两个就是sz-2
③如果当前前缀(整个串)的父节点不存在需要新建时,会把上一轮前缀(整个串)的fa链上第一个加上当前字母对应的节点存在的节点的所有信息都赋值给产生的新的父节点,博客中是结构体形式把父边关系,后缀自动机的边全部赋值了,而我用了数组,然后只复制了fa[],而后缀边却没有连。
假设我们已经把一个串后缀自动机构建好了,如何求两个串最长公共前缀呢?
我们知道后缀自动机上任何一个节点沿着后缀边跑形成的都是该串的子串,
让另一个串从根节点出发,枚举当前位对应后缀边是否存在,若存在则走到那个节点,当前累计公共前缀长度+1,枚举下一位。若每个点枚举下一位对应的后缀边不存在,意味着当前串1中没有当前的子串,此时跳fa边,可以最大利用已匹配后缀,因为当前节点的所有串右端点集合都相同,而fa点是和当前节点相同后缀最多的且右端点集合更多更有可能存在该后缀边的点,每往回跳一次,目前累计最长公共前缀变成len[fa],因为再上一个节点匹配数目肯定超过len[fa]。
代码:
#include<cstdio>
#include<cstring>
#include<iostream>
#include<queue>
#include<set>
#include<map>
#include<algorithm>
using namespace std;
#define ll long long
#define ull unsigned long long
#define for1(i,a,b) for (int i=a;i<=b;i++)
#define for0(i,a,b) for (int i=a;i<b;i++)
#define rof1(i,a,b) for (int i=a;i>=b;i--)
#define rof0(i,a,b) for (int i=a;i>b;i--)
#define pb push_back
#define pf push_front
#define fi first
#define se second
#define debug(x) printf("----Line %s----\n",#x)
#define pt(x,y) printf("%s = %d\n",#x,y)
#define INF 0x3f3f3f3f
#define dfl(x) ll x;scanf("%I64d",&x)
#define df2l(x,y) ll x,y;scanf("%I64d %I64d",&x,&y)
#define df(x) int x;scanf("%d",&x);
#define df2(x,y) int x,y;scanf("%d %d",&x,&y)
#define mod 1000000007
#define duozu(T) int T;scanf("%d",&T);while (T--)
const int N = 250000+5;
char s[N];
const int MAXN = 250000+5;
const int ALP = 26;
struct SAM
{
int trie[MAXN<<1][ALP];
int len[MAXN<<1];
int fa[MAXN<<1];
int sz,las;
void init(){
newnode(las=sz=0);
fa[0] = -1;
}
int newnode(int x){
memset(trie[x],0,sizeof trie[x]);
len[x] = 0;
return sz++;
}
int idx(char ch){
return ch-'a';
}
void add(char ch){
int c = idx(ch);
int p = las;
int np = newnode(sz);
las = np;
len[np] = len[p]+1;
for(;~p && !trie[p][c];p=fa[p]) trie[p][c] = np;/**这里循环停下来其实很关键的,停下来的话证明上一个后缀+C的对应的串存在,那个玩意儿就是现在的最长后缀了,不会更长了,否则之前就找到末尾+c有指向的点了*/
if (p==-1) fa[np] = 0;
else {
int q = trie[p][c];
if (len[q]==len[p]+1) fa[np] = q;
else {
int nq = newnode(sz);
fa[nq] = fa[q];
for0(i,0,ALP) trie[nq][i] = trie[q][i];
len[nq] = len[p]+1;
fa[np] = fa[q] = nq;
for (;~p && trie[p][c]==q;p=fa[p]) trie[p][c] = nq;/**之前所有连到q的现在全部连到nq,因为要连到最短后缀,只需证明+C小于等于nq即可,显然的*/
}
}
}
int find(char *s){
int u=0,maxlen=0,nowlen=0;
for (int i=0;s[i];i++){
int c = idx(s[i]);
while (~u && !trie[u][c]){
u = fa[u];
if (~u) nowlen = len[u];
}
if (u==-1) {nowlen = u = 0;continue;}
u = trie[u][c];
nowlen++;
maxlen = max(maxlen,nowlen);
}
return maxlen;
}
}sam;
int main()
{
//freopen("C:/Users/DELL/Desktop/input.txt", "r", stdin);
//freopen("C:/Users/DELL/Desktop/output.txt", "w", stdout);
while (~scanf("%s",s)){
sam.init();
for (int i=0;s[i];i++) sam.add(s[i]);
scanf("%s",s);
printf("%d\n",sam.find(s));
}
return 0;
}