版权声明:本文为蒟蒻原创文章,转载请注明出处哦~ https://blog.csdn.net/a54665sdgf/article/details/83215586
题意:给你n个字符串,让你找出一个最短的字符串,使该字符串在且仅在第一个字符串中出现。如果有多解,输出字典序最小的。
后缀数组解法:将这n个字符串用一种没有出现的字符作为分隔连在一起求后缀数组,把起始点在第一个字符串中的后缀放在集合A里,把其他的后缀放在集合B里,对于集合A中的每一个后缀,从集合B中找到和它名次最接近的一个或两个后缀,求出它们的lcp,则长度大于lcp的子串都是在集合B中不存在的,依次更新最短长度即可。找最接近的后缀可以利用二分(O(nlog(n)))或单调性(O(n)),两者速度没有明显差别。
#include<bits/stdc++.h>
using namespace std;
const int INF=0x3f3f3f3f;
const int N=3e5+10;
char s[N],buf[N];
int n,k,len1,m,kase=0;
vector<int> v1,v2;
struct SA
{
static const int N=3e5+10;
int bufa[N],bufb[N],c[N],sa[N],rnk[N],height[N],ST[N][20];
void getsa(char* s,int n,int m=300)
{
int *x=bufa,*y=bufb;
x[n]=y[n]=-1;
for(int i=0; i<m; ++i)c[i]=0;
for(int i=0; i<n; ++i)c[x[i]=s[i]]++;
for(int i=1; i<m; ++i)c[i]+=c[i-1];
for(int i=n-1; i>=0; --i)sa[--c[x[i]]]=i;
for(int k=1,p=0; k<n; k<<=1,m=p,p=0)
{
for(int i=n-k; i<n; ++i)y[p++]=i;
for(int i=0; i<n; ++i)if(sa[i]>=k)y[p++]=sa[i]-k;
for(int i=0; i<m; ++i)c[i]=0;
for(int i=0; i<n; ++i)c[x[y[i]]]++;
for(int i=1; i<m; ++i)c[i]+=c[i-1];
for(int i=n-1; i>=0; --i)sa[--c[x[y[i]]]]=y[i];
swap(x,y),x[sa[0]]=0,p=1;
for(int i=1; i<n; ++i)x[sa[i]]=y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k]?p-1:p++;
if(p>=n)break;
}
}
void getheight(char* s,int n)
{
for(int i=0; i<n; ++i)rnk[sa[i]]=i;
for(int i=0,k=0; i<n; ++i)
{
if(!rnk[i])continue;
if(k)k--;
while(s[i+k]==s[sa[rnk[i]-1]+k])k++;
height[rnk[i]]=k;
}
height[0]=height[n]=0;
}
void initST(int n)
{
for(int i=0; i<n; ++i)ST[i][0]=height[i];
for(int j=1; (1<<j)<=n; ++j)
for(int i=0; i+(1<<j)-1<n; ++i)
ST[i][j]=min(ST[i][j-1],ST[i+(1<<(j-1))][j-1]);
}
void build(char* s,int n)
{
getsa(s,n);
getheight(s,n);
initST(n);
}
int lcp(int l,int r)
{
if(l==r)return n-sa[l];
if(l>r)swap(l,r);
l++;
int k=0;
while(1<<(k+1)<=r-l+1)k++;
return min(ST[l][k],ST[r-(1<<k)+1][k]);
}
void solve()
{
v1.clear();
v2.clear();
for(int i=0; i<n; ++i)
{
if(sa[i]<len1)v1.push_back(sa[i]);
else v2.push_back(sa[i]);
}
int ansi,anslen=INF;
for(int i=0,j=0; i<v1.size(); ++i)
{
while(j<v2.size()&&rnk[v2[j]]<rnk[v1[i]])++j;
int maxlen=-1;
if(j<n)maxlen=max(maxlen,lcp(rnk[v1[i]],rnk[v2[j]]));
if(j>0)maxlen=max(maxlen,lcp(rnk[v1[i]],rnk[v2[j-1]]));
if(maxlen<anslen&&v1[i]+maxlen<len1)anslen=maxlen,ansi=v1[i];
}
printf("Case #%d: ",++kase);
if(anslen!=INF)for(int i=ansi; i<=ansi+anslen; ++i)printf("%c",s[i]);
else printf("Impossible");
printf("\n");
}
} sa;
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
n=0;
scanf("%d",&m);
for(int i=0; i<m; ++i)
{
scanf("%s",buf);
int len=strlen(buf);
if(i==0)len1=len;
else s[n++]='z'+1;
for(int i=0; i<len; ++i)s[n++]=buf[i];
}
s[n]='\0';
sa.build(s,n);
sa.solve();
}
return 0;
}
后缀自动机解法:对2-n个字符串建立后缀自动机,依旧是用没有出现的字符作为分隔,然后拿第一个字符串在自动机上匹配,每次走到一个失配点,就找到这个失配点对应的长度最小的子串,更新答案即可。
后缀自动机对于子串字典序的处理不如后缀数组优秀,更新字典序要在原串上一个一个字符比对,接近于半暴力。不过对于一般的数据平均复杂度应该是O(1)的,再加上总的复杂度为O(n),所以在时间上还是要略胜后缀数组一筹。
#include<bits/stdc++.h>
using namespace std;
const int INF=0x3f3f3f3f;
const int N=3e5+10;
char s1[N],buf[N];
int m,len1,kase=0;
struct SAM
{
static const int N=5e5+10;
static const int M=27;
static const int root=0;
int go[N][M],pre[N],maxlen[N],nnode,last,anslen,ansr;
void init()
{
last=nnode=0;
newnode(0);
pre[root]=-1;
}
int newnode(int l)
{
memset(go[nnode],0,sizeof go[nnode]);
maxlen[nnode]=l;
return nnode++;
}
void extend(int ch)
{
int p=last,np=last=newnode(maxlen[p]+1);
while(~p&&!go[p][ch])go[p][ch]=np,p=pre[p];
if(!~p)pre[np]=root;
else
{
int q=go[p][ch];
if(maxlen[q]==maxlen[p]+1)pre[np]=q;
else
{
int nq=newnode(maxlen[p]+1);
memcpy(go[nq],go[q],sizeof go[nq]);
pre[nq]=pre[q],pre[q]=pre[np]=nq;
while(go[p][ch]==q)go[p][ch]=nq,p=pre[p];
}
}
}
void solve()
{
anslen=INF;
for(int i=0,p=0; i<len1; ++i)
{
int ch=s1[i]-'a';
while(~p&&!go[p][ch])
{
int minlen=p?maxlen[pre[p]]+1:0;
minlen++;
if(minlen<anslen||(minlen==anslen&&strncmp(s1+i-minlen+1,s1+ansr-minlen+1,minlen)<0))ansr=i,anslen=minlen;
p=pre[p];
}
if(!~p)p=root;
p=go[p][ch];
}
printf("Case #%d: ",++kase);
if(anslen==INF)printf("Impossible\n");
else
{
for(int i=ansr-anslen+1; i<=ansr; ++i)printf("%c",s1[i]);
printf("\n");
}
}
} sam;
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
sam.init();
scanf("%d",&m);
scanf("%s",s1);
len1=strlen(s1);
for(int i=1; i<m; ++i)
{
scanf("%s",buf);
int len=strlen(buf);
if(i>1)sam.extend('z'-'a'+1);
for(int j=0; j<len; ++j)sam.extend(buf[j]-'a');
}
sam.solve();
}
return 0;
}