bzoj 2946 [Poi2000]公共串
LINK:公共串
给定n个串 求最长公共子串的长度。
可以广义SAM 求出类似于right集的表示分属某个串的东西可以直接暴力跳 当然这里n只有5 所以可以状压一下用按位或 来做 最后扫一下所有节点就行了。
但我打算使用SA来做 串在一起求SA 经典做法是二分 因为很难找到答案。
但是分析性质 可以发现最长的子串连在一起的串肯定少 我们可以利用尺取法来做。
分析答案的位置 不难发现尺取法刚好可以找到答案。
const int MAXN=20010;
int n,m,cnt,sum,ans,l,r;
int h[MAXN],s[MAXN],vis[MAXN],w[MAXN];
int pos[MAXN],x[MAXN],y[MAXN],c[MAXN],sa[MAXN],rk[MAXN];
char a[MAXN],b[MAXN];
inline void SA()
{
m=150;
rep(1,cnt,i)++c[x[i]=a[i]];
rep(1,m,i)c[i]+=c[i-1];
fep(cnt,1,i)sa[c[x[i]]--]=i;
for(int k=1;k<=cnt;k=k<<1)
{
int num=0;
rep(cnt-k+1,cnt,i)y[++num]=i;
rep(1,cnt,i)if(sa[i]>k)y[++num]=sa[i]-k;
rep(1,m,i)c[i]=0;
rep(1,cnt,i)++c[x[i]];
rep(1,m,i)c[i]+=c[i-1];
fep(cnt,1,i)sa[c[x[y[i]]]--]=y[i];
rep(1,cnt,i)y[i]=x[i],x[i]=0;
x[sa[1]]=num=1;
rep(2,cnt,i)x[sa[i]]=y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k]?num:++num;
if(num==cnt)break;
m=num;
}
//rep(1,cnt,i)printf("%d ",sa[i]);
rep(1,cnt,i)rk[sa[i]]=i;
}
inline void get_H()
{
int k=0;
rep(1,cnt,i)
{
if(rk[i]==1)continue;
if(k)--k;//h[i]>=h[i-1]-1
int j=sa[rk[i]-1];
while(a[i+k]==a[j+k])++k;
h[rk[i]]=k;
}
}
int main()
{
freopen("1.in","r",stdin);
gt(n);
rep(1,n,i)
{
scanf("%s",b+1);
int len=strlen(b+1);
rep(1,len,j)a[++cnt]=b[j],pos[cnt]=i;
a[++cnt]='z'+1;
}
a[cnt]=0;--cnt;
if(n==1){printf("%d\n",cnt);return 0;}
SA();get_H();
//rep(1,cnt,i)put(rk[i]);
int L=1,R=1;l=1;
while(R<=cnt)
{
if(sum<n)
{
++R;int x=sa[R],y=sa[R-1];
if(pos[x])++vis[pos[x]],sum+=(vis[pos[x]]==1);
if(pos[y])++vis[pos[y]],sum+=(vis[pos[y]]==1);
while(l<=r&&h[R]<=s[r])--r;
s[++r]=h[R];w[r]=R;
}
while(sum==n)
{
ans=max(ans,s[l]);++L;
int x=sa[L],y=sa[L-1];
if(pos[x])--vis[pos[x]],sum-=(!vis[pos[x]]);
if(pos[y])--vis[pos[y]],sum-=(!vis[pos[y]]);
while(w[l]<=L&&l<=r)++l;
}
}
printf("%d\n",ans);
return 0;
}
坑点:最后一个位置没用 但是必要的是清0 n可能等于1 后缀数组可能手残打错 单调队列两个条件都要加上。