后缀数组的应用

后缀排序

题目链接: P3809【模板】后缀排序
sa[i]表示排名为i的后缀的起始位置的下标

#include<bits/stdc++.h>
using namespace std;
const int maxx = 1e6+10;
char s[maxx];
int y[maxx],x[maxx],c[maxx],sa[maxx];
int rk[maxx],height[maxx],wt[30];
int n,m;
void get_sa()
{
    for(int i=1;i<=n;i++)c[x[i]=s[i]]++;
    //c数组是桶
    //x[i]是第i个元素的第一关键字
    for(int i=2;i<=m;i++)c[i]+=c[i-1];
    for(int i=n;i>=1;i--)sa[c[x[i]]--]=i;
    for(int k=1;k<=n;k<<=1)
    {
        int num=0;
        for(int i=n-k+1;i<=n;i++)y[++num]=i;
        //y[i]表示第二关键字排名为i的数,第一关键字的位置
        //第n-k+1到第n位是没有第二关键字的,所以排名在最前面
        for(int i=1;i<=n;i++)if(sa[i]>k)y[++num]=sa[i]-k;
        for(int i=1;i<=m;i++)c[i]=0;
        for(int i=1;i<=n;i++)c[x[i]]++;
        for(int i=2;i<=m;i++)c[i]+=c[i-1];
        for(int i=n;i>=1;i--)sa[c[x[y[i]]]--]=y[i],y[i]=0;
        //因为y的顺序是按照第二关键字的顺序来排的
        //第二关键字靠后的,在同一个关键字桶中排名越靠后
        //基数排序
        swap(x,y);
        x[sa[1]]=1;num=1;
        for(int i=2;i<=n;i++)
            x[sa[i]]=(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k])?num:++num;
        if(num==n)break;
        m=num;
    }
    for(int i=1;i<=n;i++)
        printf("%d ",sa[i]);
    printf("
");
}
void getheight()
{
    int k=0;
    for(int i=1;i<=n;i++)rk[sa[i]]=i;
    for(int i=1;i<=n;i++)
    {
        if(rk[i]==1)continue;
        if(k)k--;
        int j=sa[rk[i]-1];
        while(j+k<=n&&i+k<=n&&s[i+k]==s[j+k])k++;
        height[rk[i]]=k;
    }
}
int main()
{
    scanf("%s",s+1);
    n=strlen(s+1);m=122;
    get_sa();
    return 0;
}

求最大公共前缀

rk[i]就表示起始位置的下标为i的后缀的排名
height[i]表示排名为i-1的后缀和排名为i的后缀的最大公共前缀
height[i]有一个很重要的性质
定义LCP(i,j)为排名为i的后缀和排名为j的后缀的最大公共前缀
LCP(i,k)=min(LCP[i,i+1],LCP[i+1,i+2]...LCP[k-1,k])
LCP[i,k]=min(height[i+1],height[i+2]...height[k])

void get_height()
{
    int k=0;
    for(int i=1;i<=n;i++)rk[sa[i]]=i;
    for(int i=1;i<=n;i++)
    {
        if(rk[i]==1)continue;
        if(k)k--;
        int j=sa[rk[i]-1];
        while(j+k<=n&&i+k<=n&&s[i+k]==s[j+k])k++;
        height[rk[i]]=k;
    }
}

后缀数组的应用

求两个字符串的最长公共子串

题目链接: poj2774 Long Long Message
思路:将两个字符串连起来,中间用特殊符号隔开,然后跑后缀数组,求满足sa[i-1]和sa[i]在特殊符号两边的height[i]的最大值

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
const int maxx = 1e6+10;
char s[maxx];
int y[maxx],x[maxx],c[maxx],sa[maxx];
int rk[maxx],height[maxx];
int n,m;
void get_sa()
{
    for(int i=1;i<=m;i++)c[i]=0;
    for(int i=1;i<=n;i++)c[x[i]=s[i]]++;
    for(int i=2;i<=m;i++)c[i]+=c[i-1];
    for(int i=n;i>=1;i--)sa[c[x[i]]--]=i;
    for(int k=1;k<=n;k<<=1)
    {
        int num=0;
        for(int i=n-k+1;i<=n;i++)y[++num]=i;
        for(int i=1;i<=n;i++)if(sa[i]>k)y[++num]=sa[i]-k;
        for(int i=1;i<=m;i++)c[i]=0;
        for(int i=1;i<=n;i++)c[x[i]]++;
        for(int i=2;i<=m;i++)c[i]+=c[i-1];
        for(int i=n;i>=1;i--)sa[c[x[y[i]]]--]=y[i],y[i]=0;
        swap(x,y);
        x[sa[1]]=1;num=1;
        for(int i=2;i<=n;i++)
            x[sa[i]]=(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k])?num:++num;
        if(num==n)break;
        m=num;
    }
}
void get_height()
{
    int k=0;
    for(int i=1;i<=n;i++)rk[sa[i]]=i;
    for(int i=1;i<=n;i++)
    {
        if(rk[i]==1)continue;
        if(k)k--;
        int j=sa[rk[i]-1];
        while(j+k<=n&&i+k<=n&&s[i+k]==s[j+k])k++;
        height[rk[i]]=k;
    }
}
int main()
{
    scanf("%s",s+1);
    int len1=strlen(s+1);
    s[++len1]='0';
    scanf("%s",s+len1+1);
    int len2=strlen(s+1);
    m=122;n=len2;
    get_sa();
    get_height();
    int ans=0;
    for(int i=2;i<=len2;i++)
    {
        if(height[i]>ans)
        {
            if((len1<sa[i]&&len1>sa[i-1])||(len1>sa[i]&&len1<sa[i-1]))
                ans=height[i];
        }
    }
    printf("%d
",ans);
    return 0;
}

求多个字符串的最长公共子串

题目链接: poj3450 Corporate Identity
思路:将每个字符串用特殊字符分隔连成一串,然后二分枚举长度,通过height[i]判断是否有n个字符串的最长公共子串为这个长度

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int maxx = 800010;
char ch[210];
int y[maxx],x[maxx],c[maxx],sa[maxx];
int rk[maxx],height[maxx];
int s[maxx];
int pos[maxx],vis[4010];
int n,m,t,anspos;
void get_sa()
{
    for(int i=1;i<=m;i++)c[i]=0;
    for(int i=1;i<=n;i++)c[x[i]=s[i]]++;
    for(int i=2;i<=m;i++)c[i]+=c[i-1];
    for(int i=n;i>=1;i--)sa[c[x[i]]--]=i;
    for(int k=1;k<=n;k<<=1)
    {
        int num=0;
        for(int i=n-k+1;i<=n;i++)y[++num]=i;
        for(int i=1;i<=n;i++)if(sa[i]>k)y[++num]=sa[i]-k;
        for(int i=1;i<=m;i++)c[i]=0;
        for(int i=1;i<=n;i++)c[x[i]]++;
        for(int i=2;i<=m;i++)c[i]+=c[i-1];
        for(int i=n;i>=1;i--)sa[c[x[y[i]]]--]=y[i],y[i]=0;
        swap(x,y);
        x[sa[1]]=1;num=1;
        for(int i=2;i<=n;i++)
            x[sa[i]]=(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k])?num:++num;
        if(num==n)break;
        m=num;
    }
}
void get_height()
{
    int k=0;
    for(int i=1;i<=n;i++)rk[sa[i]]=i;
    for(int i=1;i<=n;i++)
    {
        if(rk[i]==1)continue;
        if(k)k--;
        int j=sa[rk[i]-1];
        while(j+k<=n&&i+k<=n&&s[i+k]==s[j+k])k++;
        height[rk[i]]=k;
    }
}
int judge(int mid)
{
    int i=2;
    for(int i=2;i<=n;i++)
    {
        if(height[i]<mid)continue;
        int cnt=0;
        for(int j=1;j<=t;j++)vis[j]=0;
        while(height[i]>=mid&&i<=n)
        {
            if(!vis[pos[sa[i-1]]])
            {
                vis[pos[sa[i-1]]]=1;
                cnt++;
            }
            i++;
        }
        if(!vis[pos[sa[i-1]]])
        {
            vis[pos[sa[i-1]]]=1;
            cnt++;
        }
        if(cnt>=t)
        {
            anspos=sa[i-1];
            return 1;
        }
    }
    return 0;
}
int main()
{
    while(~scanf("%d",&t)&&t)
    {
        m=30;
        n=0;
        int len,ma=210;
        for(int i=1;i<=t;i++)
        {
            scanf("%s",ch);
            len=strlen(ch);
            ma=min(ma,len);
            for(int j=0;j<len;j++)
            {
                s[++n]=ch[j]-'a'+1;
                pos[n]=i;
            }
            s[++n]=++m;
        }
        get_sa();
        get_height();
        int l=1,r=ma;
        int ans=0;
        while(l<=r)
        {
            int mid=(l+r)/2;
            if(judge(mid))ans=mid,l=mid+1;
            else r=mid-1;
        }
        if(ans==0)printf("IDENTITY LOST
");
        else
        {
            for(int i=anspos;i<anspos+ans;i++)
                printf("%c",s[i]+'a'-1);
            printf("
");
        }
    }
    return 0;
}

求多个字符串中不少于k个字符串的最长公共子串

题目链接:poj3294 Life Forms
思路:跟上面一样,判断的时候只要判断是否有超过n/2个字符串的的最长公共子串为该长度,这道题还要特判一下n=1

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int maxx = 1000010;
char ch[1010];
int y[maxx],x[maxx],c[maxx],sa[maxx];
int rk[maxx],height[maxx];
int s[maxx];
int pos[maxx],vis[110];
int n,m,t,anspos[maxx];
void get_sa()
{
    for(int i=1;i<=m;i++)c[i]=0;
    for(int i=1;i<=n;i++)c[x[i]=s[i]]++;
    for(int i=2;i<=m;i++)c[i]+=c[i-1];
    for(int i=n;i>=1;i--)sa[c[x[i]]--]=i;
    for(int k=1;k<=n;k<<=1)
    {
        int num=0;
        for(int i=n-k+1;i<=n;i++)y[++num]=i;
        for(int i=1;i<=n;i++)if(sa[i]>k)y[++num]=sa[i]-k;
        for(int i=1;i<=m;i++)c[i]=0;
        for(int i=1;i<=n;i++)c[x[i]]++;
        for(int i=2;i<=m;i++)c[i]+=c[i-1];
        for(int i=n;i>=1;i--)sa[c[x[y[i]]]--]=y[i],y[i]=0;
        swap(x,y);
        x[sa[1]]=1;num=1;
        for(int i=2;i<=n;i++)
            x[sa[i]]=(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k])?num:++num;
        if(num==n)break;
        m=num;
    }
}
void get_height()
{
    int k=0;
    for(int i=1;i<=n;i++)rk[sa[i]]=i;
    for(int i=1;i<=n;i++)
    {
        if(rk[i]==1)continue;
        if(k)k--;
        int j=sa[rk[i]-1];
        while(j+k<=n&&i+k<=n&&s[i+k]==s[j+k])k++;
        height[rk[i]]=k;
    }
}
int judge(int mid)
{
    int i=2,tot=0;
    for(int i=2;i<=n;i++)
    {
        if(height[i]<mid)continue;
        int cnt=0;
        for(int j=1;j<=t;j++)vis[j]=0;
        while(height[i]>=mid&&i<=n)
        {
            if(!vis[pos[sa[i-1]]])
            {
                vis[pos[sa[i-1]]]=1;
                cnt++;
            }
            i++;
        }
        if(!vis[pos[sa[i-1]]])
        {
            vis[pos[sa[i-1]]]=1;
            cnt++;
        }
        if(cnt>t/2)anspos[++tot]=sa[i-1];
    }
    if(tot)
    {
        anspos[0]=tot;
        return 1;
    }
    else return 0;
}
int main()
{
    int cas=0;
    while(~scanf("%d",&t)&&t)
    {
        if(cas++)printf("
");
        if(t==1)
        {
            scanf("%s",ch);
            printf("%s
",ch);
            continue;
        }
        m=30;
        n=0;
        int len,ma=0;
        for(int i=1;i<=t;i++)
        {
            scanf("%s",ch);
            len=strlen(ch);
            ma=max(ma,len);
            for(int j=0;j<len;j++)
            {
                s[++n]=ch[j]-'a'+1;
                pos[n]=i;
            }
            s[++n]=++m;
        }
        get_sa();
        get_height();
        int l=1,r=ma;
        int ans=0;
        while(l<=r)
        {
            int mid=(l+r)/2;
            if(judge(mid))ans=mid,l=mid+1;
            else r=mid-1;
        }
        if(ans==0)printf("?
");
        else
        {
            for(int i=1;i<=anspos[0];i++)
            {
                for(int j=anspos[i];j<anspos[i]+ans;j++)
                    printf("%c",s[j]+'a'-1);
                printf("
");
            }
        }
    }
    return 0;
}
原文地址:https://www.cnblogs.com/HooYing/p/11779976.html