【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(要接新子串)。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#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; }