bzoj2946 [Poi2000]公共串
原题地址:http://www.lydsy.com/JudgeOnline/problem.php?id=2946
题意:
给出几个由小写字母构成的单词,求它们最长的公共子串的长度。
任务:
- 读入单词
- 计算最长公共子串的长度
- 输出结果
数据范围
1<=n<=5,单词长度<=2000
题解:
发现不会写SA了,回忆一发。
对于SA来说,求多个串的公共串是一个比较好处理的问题,
把所有串用不同间隔符连起来,跑后缀数组。
二分长度,对height分组,然后看一个组内是不是包含所有的串。
如果用SAM处理这个问题:
法一:
抄论文
将所有S_i连接在一起成为一个新的字符串T,其中每个S_i后要加上一个不同的分隔符D_i(即加上K个额外的不同特殊字符D_1~D_K):
我们对字符串T构建后缀自动机。
现在我们需要在后缀自动机找出一个字符串,它是所有字符串S_i的子串。注意到如果一个子串在某个字符串S_j中出现过,那么后缀自动机中存在一
条以这个子串为前缀的路径,包含分隔符D_j,但不包含其他分隔符D_1,…,D_j-1,D_j+1,…,D_k。
因此,我们需要计算“可达性”:对自动机中的每个状态和每个字符D_i,计算是否有一条从该状态开始的路径,包含分隔符D_i,但不包含别的分隔符。
很容易用DFS/BFS或者动态规划实现。在此之后,原问题的答案就是字符串longest(v),其中v能够到达所有的分隔符。
法二:
对第一个串建SAM,然后其他的串在上面跑,失配跳parent,得到每个状态匹配每个串的最长匹配长度。
然后每个状态的每个串取最小值,每个状态取最大值。
这里有一个要注意的地方,正如AC自动机的节点需要更新到fail一样,
在SAM上也可能出现这个串走了这个节点,另一个串走到它的pa,而没有更新到的情况,
所以还要按拓扑序把它的答案更新到它的pa上。
这样不会多算,因为本身由于是第一个串建的SAM,一个状态i的最大值也不过是len[i]。
SA代码:
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=100010;
const int M=2005;
int a[N],b[N],w[N],sa[N],rk[N],ht[N],s[N],pos[N];
bool vis[6];
char str[6][M];
void getsa(int n)
{
int *x=a; int *y=b; int m=127;//x是rank,rank有重
for(int i=0;i<=n;i++) w[x[i]=s[i]]++;
for(int i=1;i<=127;i++) w[i]+=w[i-1];
for(int i=n;i>=0;i--) sa[--w[x[i]]]=i;
for(int i=0;i<=127;i++) w[i]=0;
for(int l=1,p=0;l<=n;l=l<<1) //y为第二关键字sa
{
for(int i=n-l+1;i<=n;i++) y[p++]=i;
for(int i=0;i<=n;i++) if(sa[i]-l>=0) y[p++]=sa[i]-l;
//然后求新sa
for(int i=0;i<=n;i++) w[x[i]]++;
for(int i=1;i<=m;i++) w[i]+=w[i-1];
for(int i=n;i>=0;i--) sa[--w[x[y[i]]]]=y[i];
for(int i=0;i<=m;i++) w[i]=0;
//然后求新rank
swap(x,y); //现在y是旧rank,sa是新sa,求新rank
p=1; x[sa[0]]=0;
for(int i=1;i<=n;i++) x[sa[i]]= (y[sa[i]]==y[sa[i-1]]&&y[sa[i]+l]==y[sa[i-1]+l])?p-1:p++;
m=p; p=0;
if(p>n) break;
}
}
void getheight(int n)
{
int k=0;
for(int i=0;i<=n;i++) rk[sa[i]]=i;
ht[rk[0]]=0;
for(int i=0;i<n;i++)
{
if(k) k--;
int j=sa[rk[i]-1];
while(s[i+k]==s[j+k]) k++;
ht[rk[i]]=k;
}
}
bool check(int k,int n,int m)
{
memset(vis,0,sizeof(vis));
for(int i=2;i<=n;i++)
{
if(ht[i]>=k) vis[pos[sa[i]]]=1;
else
{
bool flag=0; for(int j=1;j<=m;j++) {if(!vis[j]) flag=1; vis[j]=0;}
if(!flag) return 1;
vis[pos[sa[i]]]=1;
}
}
bool flag=0;
for(int i=1;i<=m;i++) {if(!vis[i]) flag=1; vis[i]=0;}
if(!flag) return 1;
return 0;
}
int main()
{
int n; int len=0;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%s",str[i]);
int l=strlen(str[i]);
for(int j=0;j<l;j++) {pos[len]=i; s[len++]=str[i][j];}
if(i!=n) s[len++]=i;
}
getsa(len);
getheight(len);
int lf=0; int rg=len;
while(lf+1<rg)
{
int mid=(lf+rg)>>1;
if(check(mid,len,n)) lf=mid;
else rg=mid;
}
if(check(rg,len,n)) printf("%d\n",rg);
else printf("%d\n",lf);
return 0;
}
SAM代码:
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=10005;
char s[N];
struct node
{
int ch[26],pa;
}tr[N];
int len[N],last=1,tail=1,root=1,n,ans[N],ret[N],q[N],w[N];
void insert(int c)
{
int nd=++tail; len[nd]=len[last]+1; int tmp=last;
for(;tmp&&!tr[tmp].ch[c];tmp=tr[tmp].pa) tr[tmp].ch[c]=nd;
if(!tmp) tr[nd].pa=root;
else
{
int B=tr[tmp].ch[c];
if(len[B]==len[tmp]+1) tr[nd].pa=B;
else
{
int nB=++tail; tr[nB]=tr[B]; len[nB]=len[tmp]+1;
for(;tmp&&tr[tmp].ch[c]==B;tmp=tr[tmp].pa) tr[tmp].ch[c]=nB;
tr[B].pa=tr[nd].pa=nB;
}
}
last=nd;
}
void gettopu(int L)
{
for(int i=1;i<=tail;i++) ans[i]=len[i];
for(int i=1;i<=tail;i++) w[len[i]]++;
for(int i=1;i<=L;i++) w[i]+=w[i-1];
for(int i=1;i<=tail;i++) q[w[len[i]]--]=i;
}
void getans()
{
int L=strlen(s); int cur=0; int tmp=root;
memset(ret,0,sizeof(ret));
for(int i=0;i<L;i++)
{
int c=s[i]-'a';
while(tmp&&!tr[tmp].ch[c]) tmp=tr[tmp].pa;
if(!tmp) {tmp=1; cur=0;}
else {cur=min(cur,len[tmp])+1; tmp=tr[tmp].ch[c];}
ret[tmp]=max(ret[tmp],cur);
}
for(int i=tail;i>=1;i--) ret[tr[q[i]].pa]=max(ret[q[i]],ret[tr[q[i]].pa]);
for(int i=1;i<=tail;i++) ans[i]=min(ret[i],ans[i]);
}
int main()
{
scanf("%d",&n);
scanf("%s",s); int L=strlen(s);
for(int i=0;i<L;i++) insert(s[i]-'a');
gettopu(L);
for(int i=1;i<n;i++) {scanf("%s",s); getans();}
int answer=0; for(int i=1;i<=tail;i++) answer=max(answer,ans[i]);
printf("%d\n",answer);
return 0;
}