【BZOJ】4032: [HEOI2015]最短不公共子串(LibreOJ #2123)

【题意】给两个小写字母串A,B,请你计算:

(1) A的一个最短的子串,它不是B的子串

(2) A的一个最短的子串,它不是B的子序列

(3) A的一个最短的子序列,它不是B的子串

(4) A的一个最短的子序列,它不是B的子序列

不存在输出-1,1<=len(A),len(B)<=2000。

【算法】后缀自动机+序列自动机

【题解】虽然网上题解很多,但我总觉得这四个问题其实可以一个统一的形式来回答。因为字符串的自动机本质是相同的。

对串B建立后缀自动机来识别子串,建立序列自动机来识别子序列,从左到右枚举A串并在B自动机上进行。(序列自动机没有fail边,但这里不需要)

 

先考虑识别串A的子序列,设$f_x$表示自动机中节点x识别到的A的最短子序列。

对于A的子序列,从左到右枚举当前字母c,对B自动机中的每个节点都进行转移,假设x+c=y,那么:

$$f_y=min\{ f_y,f_x+1\}$$

如果y=null,那么贡献答案$ans=min\{ ans,f_x+1\}$。

原理是:字母c可以接在自动机识别了的所有子序列的后面形成新的子序列。

这里要注意更新顺序,为了满足无后效性,序列自动机要从后往前更新,后缀自动机要按Parent树从下往上更新(trans边不可能返祖)。

 

在考虑识别串A的子串,c只能接在所有以c前一位结尾的子串后面,那么只要每次转移到$f_y$时初始化$f_x=inf$即可。另外注意根节点不能置为inf(要接新子串)。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=2010,inf=0x3f3f3f3f;
int n,m,last,size,root,pre[maxn],ch[maxn][26],f[maxn*2],w[maxn],b[maxn*2];
char s[maxn],a[maxn];
struct tree{int len,fa,t[26];}t[maxn*2];//
void insert_SAM(int c){
    int np=++size;
    t[np].len=t[last].len+1;
    int x=last;
    last=np;
    while(x&&!t[x].t[c])t[x].t[c]=np,x=t[x].fa;
    if(!x)t[np].fa=root;else{
        int y=t[x].t[c];
        if(t[y].len==t[x].len+1)t[np].fa=y;else{
            int nq=++size;
            t[nq]=t[y];//
            t[nq].len=t[x].len+1;
            t[nq].fa=t[y].fa;t[y].fa=t[np].fa=nq;
            while(x&&t[x].t[c]==y)t[x].t[c]=nq,x=t[x].fa;//
        }
    }
}
void build(){
    last=size=root=1;
    for(int i=1;i<=m;i++)insert_SAM(s[i]-'a');
    for(int i=1;i<=m;i++){
        int c=s[i]-'a';
        for(int j=i-1;j>=pre[c];j--)ch[j][c]=i;
        pre[c]=i;
    }
    for(int i=1;i<=size;i++)w[t[i].len]++;
    for(int i=1;i<=m;i++)w[i]+=w[i-1];
    for(int i=1;i<=size;i++)b[w[t[i].len]--]=i;
}
int trans(int x,int c,int y){
    if(!y)return t[x].t[c];
    else return ch[x][c];
}
void solve(int A,int B){
    memset(f,0x3f,sizeof(f));
    f[B^1]=0;
    int ans=inf;
    for(int i=1;i<=n;i++){
        int c=a[i]-'a';
        for(int z=(B?m:size);z>=(B^1);z--){
            int x=B?z:b[z];
            int y=trans(x,c,B);
            if(!y)ans=min(ans,f[x]+1);else{
                f[y]=min(f[y],f[x]+1);if(!A&&x!=(B^1))f[x]=inf;
            }
        }
    }
    printf("%d\n",ans==inf?-1:ans);
}        
int main(){
    scanf("%s%s",a+1,s+1);n=strlen(a+1);m=strlen(s+1);
    build();
    solve(0,0);solve(0,1);solve(1,0);solve(1,1);
    return 0;
}
View Code

 

posted @ 2018-01-04 12:32  ONION_CYC  阅读(380)  评论(0编辑  收藏  举报