bzoj2946 [Poi2000]公共串(后缀数组 || 后缀自动机)

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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值