Gym - 101194F Mr. Panda and Fantastic Beasts (后缀数组/后缀自动机)

版权声明:本文为蒟蒻原创文章,转载请注明出处哦~ 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;
}

猜你喜欢

转载自blog.csdn.net/a54665sdgf/article/details/83215586