后缀数组&&后缀自动机题集

后缀数组:

hdu-6704:https://vjudge.net/problem/HDU-6704

题意:

求(l,r)区间子串在母串中第k次出现的位置

解析:

一个子串必定是某个后缀的前缀

排序相邻的后缀他们的前缀一定最相似         =>      一种子串必定是一些排序相邻的后缀的公共前缀

用后缀数组得到sa,height,rk等.

对于求子串(1,3)的第k个的开头位置,我们得到一个rk[1],有(1,3)作为前缀的子串一定在rk[1]的前后两边

求两个后缀的最长公共前缀用rmq+height求,lcp(a,b)=min(height[a+1],....,height[b]);

我们用两个二分求出最前端和最后端,得到L,R,(L,R)里的串都的前缀都以子串(1,3)作为前缀的,我们再用主席树求出第k个的位置,即第k小的位置

ac:

#include<bits/stdc++.h>
#define MAXN 100005
using namespace std;
char s[MAXN];
int y[MAXN],x[MAXN],c[MAXN];
int sa[MAXN],rk[MAXN],height[MAXN];

/*后缀数组*********************************************************/
void get_SA(int n,int m)
{
    for(int i=0;i<MAXN;i++)
        x[i]=y[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 n)
{
    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 mins[MAXN][24];

void buildmin(int n)
{
    int tem = (int)floor(log2((double)n));
    for(int i=1;i<=n;i++)
        mins[i][0]=height[i];
    for(int j=1;j<=tem;j++)
        for(int i=1;i+(1<<j)-1<=n;i++)
            mins[i][j] = min(mins[i][j-1],mins[i+(1<<(j-1))][j-1]);
}

int getlcp(int a,int b,int n)
{
    if(a>b) swap(a,b);//要求合法
    if(a==b)
        return n-a+1;//相等直接返回
    a++;        //lcp=min(hei[l+1],....hei[r]);
    int k = log2(b-a+1);
    return min(mins[a][k],mins[b-(1<<k)+1][k]);
}
/*主席树************************************************/
int L[MAXN*25],R[MAXN*25],sum[MAXN*25],root[MAXN];
int tot=0;


int build(int l,int r)
{
    int now=++tot;
    sum[now]=0;
    if(l!=r)
    {
        int mid=(l+r)>>1;
        L[now]=build(l,mid);
        R[now]=build(mid+1,r);
    }
    return now;
}

int update(int pre,int l,int r,int k)
{
    int now=++tot;
    sum[now]=sum[pre]+1;
    L[now]=L[pre],R[now]=R[pre];
    if(l!=r)
    {
        int mid=(l+r)>>1;
        if(k<=mid)
            L[now]=update(L[pre],l,mid,k);
        else
            R[now]=update(R[pre],mid+1,r,k);
    }
    return now;
}

int query(int x,int y,int l,int r,int k)
{
    if(l==r)
        return l;
    int mid=(l+r)>>1;
    int num=sum[L[y]]-sum[L[x]];
    if(num>=k)
        return query(L[x],L[y],l,mid,k);
    else
        return query(R[x],R[y],mid+1,r,k-num);
}
/********************************************************/

void solve(int a,int b,int k,int n)
{
    int len=b-a+1;
    int L=rk[a],R=rk[a];

    int l=1,r=rk[a]-1;//以(a,b)作为子串的最前端
    while(l<=r)
    {
        int mid=(l+r)>>1;
        if(getlcp(mid,rk[a],n)>=len)
        {
            L=mid;
            r=mid-1;
        }
        else{
            l=mid+1;
        }
    }

    l=rk[a]+1,r=n;//以(a,b)作为子串的最后端
    while(l<=r)
    {
        int mid=(l+r)>>1;
        if(getlcp(mid,rk[a],n)>=len)
        {
            R=mid;
            l=mid+1;
        }
        else{
            r=mid-1;
        }
    }
    if(R-L+1<k)//没有第k
        printf("-1\n");
    else{
        printf("%d\n",query(root[L-1],root[R],1,n,k));
    }
}

void init()
{
    tot=0;
    memset(mins,0,sizeof(mins));
    memset(sum,0,sizeof(sum));
}

int main()
{
    int t,n,q,k,a,b;
    scanf("%d",&t);
    while(t--)
    {
        init();
        scanf("%d%d",&n,&q);
        int m,l,r;m=122;
        scanf("%s",s+1);
        get_SA(n,m);
        get_height(n);
        buildmin(n);
        root[0]=build(1,n);
        for(int i=1;i<=n;i++)
            root[i]=update(root[i-1],1,n,sa[i]);
        while(q--)
        {
            scanf("%d%d%d",&l,&r,&k);
            solve(l,r,k,n);
        }
    }
    return 0;
}

链接:https://vjudge.net/problem/SPOJ-DISUBSTR

题意:求母串有多少个不同的子串

解析:

每个子串一定是某个后缀的前缀,那么原问题等价于求所有后缀之间的不相同子串个数

ans=n*(n+1)/2-height[i]的和

ac:

#include<bits/stdc++.h>
#define ll long long
#define MAXN 1005
using namespace std;
char s[MAXN];
int y[MAXN],x[MAXN],c[MAXN];
int sa[MAXN],rk[MAXN],height[MAXN];

void get_SA(int n,int m)
{
    for(int i=0;i<MAXN;i++)
        x[i]=y[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 n)
{
    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()
{
    int n,m,t;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%s",s+1);
        n=strlen(s+1);m=122;
        get_SA(n,m);
        get_height(n);
        ll ans=((ll)n)*(n+1)/2;
        for(int i=2;i<=n;i++)
            ans-=height[i];
        printf("%lld\n",ans);
    }
    return 0;
}

https://vjudge.net/problem/SPOJ-REPEATs

题意:

给你一个长N的字符串,现在要你找出该字符串中具有最大循环节数目的连续子串.输出该子串的循环节数目即可

解析:

https://blog.csdn.net/u013480600/article/details/23974119

ac:

#include<bits/stdc++.h>
#define MAXN 1000005
using namespace std;
char s[MAXN];
int y[MAXN],x[MAXN],c[MAXN];
int sa[MAXN],rk[MAXN],height[MAXN];

void get_SA(int n,int m)
{
    for(int i=0;i<MAXN;i++)
        x[i]=y[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 n)
{
    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 mins[MAXN][24];

void buildmin(int n)
{
    int tem = (int)floor(log2((double)n));
    for(int i=1;i<=n;i++)
        mins[i][0]=height[i];
    for(int j=1;j<=tem;j++)
        for(int i=1;i+(1<<j)-1<=n;i++)
            mins[i][j] = min(mins[i][j-1],mins[i+(1<<(j-1))][j-1]);
}

int querymin(int a,int b)
{
    int k = log2(b-a+1);
    return min(mins[a][k],mins[b-(1<<k)+1][k]);
}

int getlcp(int a,int b,int n)
{
    a=rk[a],b=rk[b];
    if(a>b)
        swap(a,b);
    if(a==b)
        return n-a+1;
    else
        return querymin(a+1,b);
}

void init()
{
    memset(s,0,sizeof(s));
    memset(mins,0,sizeof(mins));
}

int main()
{
    int m,q,l,r,t,n;
    scanf("%d",&t);
    while(t--)
    {
        init();
        scanf("%d",&n);
        char v=getchar();
        for(int i=1;i<=n;i++)
            scanf("%c",&s[i]),v=getchar();
        s[n+1]='\0';
        m=128;
        get_SA(n,m);
        get_height(n);
        buildmin(n);
        int ans=1;
        for(int len=1;len<n;len++)
        {
            for(int j=1;j+len<=n;j+=len)
            {
                int k=getlcp(j,j+len,n);
                int now=k/len+1;
                int st=j-(len-k%len);
                if(st>=0)//合法
                {
                    if(getlcp(st,st+len,n)/len+1>now)
                        now++;
                }
                ans=max(ans,now);
            }
        }
        printf("%d\n",ans);
    }
    return 0;
}

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值