SPOJ LCS2 Longest Common Substring II
后缀自动机
题意
给出若干串,求所有串的最长公共子串。
思路
SAM。
对于第一个串建SAM,用后面的串在上面跑。开个数组统计每个状态的最大长度,每个字串搞完了后合并到一个新数组上。合并每个字串的长度时各字串之间取min。最后统计每个状态的max即可。
然后WA。。。因为每个可匹配的状态x的前驱状态y也是可以匹配的。。但是在自动机上跑的过程可能没跑到y这个状态。。。另一个字串只能匹配y状态,造成最后合并时认为xy都不能匹配。。
如何处理前驱呢?思路是在失配树(suffix link tree)上dfs。不过我们不用DFS,排序即可。因为子状态的maxlen一定大于父状态的maxlen。只要排过序,从maxlen大的开始遍历,就可以O(n)更新。
不过排序会退化成nlogn,为了避免,我们用基数排序,因为maxlen不超过字串长度n。基数排序的ji数组相当于统计每个数有多少个,id是新标号。
我因为基数排序排错了而gg好几发。。
有多个字符串(<=10)求LCS
首先对首个字符串建立后缀自动机,紧接着对每个字符串,都进行匹配,x数组处理在此之前的字符串匹配到这一个状态的LCS(取前面所有y[i]的min),y数组表示在当前字符串中匹配到这一位的最大lcs。然后每做完一个字符串就要把x数组更新。答案就是max(x[i])其中我们发现每次更新x数组的时候,要把这个状态的所有pre值都处理,为了保证线性维护,我们要先预处理出一个拓扑序列,倒着处理就可以了。处理出拓扑序列的方法不需要用常规的bfs或dfs,会超时,只要用一个类似桶排的方法按照每一个状态距离根的最长距离(a[p].step)来排就可以处理出一个拓扑序列了。话说我做这道题真是曲曲折折。第一次想到了大概做法,写完之后random对拍过,然后wa第10个(估计手出)的测试点。紧接着发现自己把x和y的数组一起处理没有考虑先后,然后改了之后还是wa第10个点,又发现每个状态的pre没有处理到。又写了dfs的拓扑发现超时,改成线性的居然还是超时。郁闷之下提交大神的代码居然也还是超时,整整耗了半个小时重新提交就AC了。论SPOJ的坑爹性,你换台好一点的评测机又不会怀孕。。。
代码
#include<bits/stdc++.h>
#define M(a,b) memset(a,b,sizeof(a))
typedef long long LL;
using namespace std;
const int MAXL=500005;
const int MAXS=26;
struct SAM
{
int amo=0, len, st;
int maxlen[2*MAXL+10], trans[2*MAXL+10][MAXS], slink[2*MAXL+10], endposamu[2*MAXL+10];
int ma[MAXL*2+10], mi[MAXL*2+10];
int new_state(int _maxlen, int _minlen, int* _trans, int _slink)
{
amo++;
maxlen[amo]=_maxlen;
for(int i=0; i<MAXS; i++)
{
if(_trans==NULL)
trans[amo][i]=0;
else
trans[amo][i]=_trans[i];
}
slink[amo]=_slink;
return amo;
}
int add_char(char ch, int u)
{
int c=ch-'a';
int z=new_state(maxlen[u]+1, -1, NULL, 0);
int v=u;
while(v!=0&&trans[v][c]==0)
{
trans[v][c]=z;
v=slink[v];
}
if(v==0)
{
slink[z]=1;
return z;
}
int x=trans[v][c];
if(maxlen[v]+1==maxlen[x])
{
slink[z]=x;
return z;
}
int y=new_state(maxlen[v]+1, -1, trans[x], slink[x]);
slink[x]=y;
slink[z]=y;
int w=v;
while(w!=0&&trans[w][c]==x)
{
trans[w][c]=y;
w=slink[w];
}
return z;
}
void init()
{
memset(maxlen, 0, sizeof(maxlen));
memset(trans, 0, sizeof(maxlen));
memset(slink, 0, sizeof(maxlen));
memset(endposamu, 0, sizeof(endposamu));
amo=0;
st=new_state(0, -1, NULL, 0);
memset(mi, 0x3f, sizeof(mi));
memset(ji, 0, sizeof(ji));
memset(id, 0, sizeof(id));
}
void addstring(char s[], int n)
{
len=n;
int la=st;
for(int i=1;i<=n;i++)
{
la=add_char(s[i], la);
}
prepare();
}
int ji[MAXL+5], id[2*MAXL+7];
void prepare()
{
ji[0]=0;
for(int i=st;i<=amo;i++) ji[maxlen[i]]++;
for(int i=1;i<=amo;i++) ji[i]+=ji[i-1];
for(int i=st;i<=amo;i++) id[ji[maxlen[i]]--]=i;
}
void match(char t[], int m)
{
memset(ma, 0, sizeof(ma));
int k=st;
int len=0;
for(int i=1;i<=m;i++)
{
int now=t[i]-'a';
if(trans[k][now])
{
k=trans[k][now];
len=len+1;
}
else
{
while(!trans[k][now]&&k)
{
k=slink[k];
len=maxlen[k];
}
if(!k) k=st, len=0;
else len=maxlen[k], k=trans[k][now], len++;
}
ma[k]=max(ma[k], len);
}
for(int i=amo;i>=st;i--)
{
int now=id[i];
mi[now]=min(mi[now], ma[now]);
if(ma[now]&&slink[now]) ma[slink[now]]=maxlen[slink[now]];
}
}
}sam;
char s[MAXL];
int main()
{
scanf("%s", s+1);
sam.init();
int n=strlen(s+1);
sam.addstring(s, n);
while(scanf("%s", s+1)==1)
{
int m=strlen(s+1);
sam.match(s, m);
}
int res=0;
for(int i=sam.st;i<=sam.amo;i++)
{
res=max(res, sam.mi[i]);
}
printf("%d\n", res);
return 0;
}