后缀数组题表

定义

1. 1. 后缀:包含最后一个字符的字串。
2. 2. 前缀:包含第一个字符的字串。
3. 3. 子串:字符串中连续的某一段。等价定义:某个后缀的前缀
4.sa[i] 4. s a [ i ] 表示排名为 i i 的后缀的起始位置。
5.rank[i] 表示起始位置为 i i 的后缀的排名。
6.lcp(i,j) 表示起始位置为 i i 的后缀和起始位置为 j 的后缀的最长公共前缀。
7.height[i]=lcp(sa[i],sa[i1]) 7. h e i g h t [ i ] = l c p ( s a [ i ] , s a [ i − 1 ] )

模板

#include<iostream>
#include<cstdio>
#include<cstring>
#define maxn 2000050
using namespace std;
int wa[maxn],wb[maxn],rs[maxn];
int sa[maxn],rank[maxn],height[maxn];
#define cmp(r,a,b,l) ((r[a]==r[b])&&(r[a+l]==r[b+l]))
void da(const char *r,int n,int m){
    int *x=wa,*y=wb;
    for(int i=1;i<=n;++i)
        ++rs[x[i]=r[i]];
    for(int i=2;i<=m;++i)
        rs[i]+=rs[i-1];
    for(int i=n;i>=1;--i)
        sa[rs[x[i]]--]=i;
    for(int j=1,p=0;j<=n&&p<n;j<<=1,m=p,p=0){
        for(int i=n-j+1;i<=n;++i)
            y[++p]=i;
        for(int i=1;i<=n;++i)
            if(sa[i]>j)
                y[++p]=sa[i]-j;
        for(int i=1;i<=m;++i) rs[i]=0;
        for(int i=1;i<=n;++i) ++rs[x[i]];
        for(int i=2;i<=m;++i) rs[i]+=rs[i-1];
        for(int i=n;i>=1;--i)
            sa[rs[x[y[i]]]--]=y[i];
        swap(x,y);
        x[sa[1]]=1;
        p=1;
        for(int i=2;i<=n;++i)
            x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p:++p;
    }
    for(int i=1; i<=n; ++i)
        rank[sa[i]]=i;
    for(int i=1,k=0; i<=n; height[rank[i++]]=(k?k--:k))
        for(int j=sa[rank[i]-1]; r[i+k]==r[j+k]; ++k);
}

以下代码省略模板。

题目

模板(P3809)
char r[maxn];
int main()
{
    scanf("%s",r+1);
    da(r,strlen(r+1),300);
    for(int i=1;r[i];i++)
        printf("%d ",sa[i]);
}
重复子串
可重叠最长重复子串(暂无)

题意:给定一个字符串,求最长重复子串,这两个子串可以重叠。
题解:对这个字符串求后缀数组,由于子串必然是某个后缀的前缀,因此可以得知:只有排序后相邻的两个后缀的的最长公共前缀才可能是答案。因此答案即为 maxi=1nheight[i] max i = 1 n h e i g h t [ i ]
代码:

char s[maxn];
int main(void)
{
    scanf("%s",s+1);
    const int l=strlen(s+1);
    da(s,l,127);
    int ans=0;
    for(int i=1;i<=l;i++)
        ans=max(ans,height[i]);
    printf("%d\n",ans);
    return 0;
}
不可重叠最长重复子串(pku 1743/Luogu P2743)

题意:给定一个序列,求等价的最长子串,且长度大于 5 5 ,不可重叠。
等价的定义:对序列整体加上某个数值后与另一个序列完全相等。
题解:可以发现,两个原序列的长度为 l+1 的等价序列,差分后,为长度为 l l 相等序列。因此问题转换为不可重叠的最长重复子串。
二分答案 k,问题转换为判定是否存在长度为 k k 的最长子串。如果继续沿用上一题的方法会出现重叠,因此对height数组分组,所有相邻的大于 k height h e i g h t 分为一组,当 k k 等于 2 时候,分组如下。
分组.png
可以发现, 有希望成为最长公共前缀不小于k的两个后缀一定在同一组。且两个后缀不会重叠当且仅当它们的 sa s a 值的差大于 k k
代码:

int n,a[MAXN],r[MAXN];
bool check(int k) {
    bool flag=0;
    int mx=-INF,mm=INF;
    for(int i=2; i<=n; ++i) {
        if(height[i]>=k) {
            //最有可能成功的是组内最长和最短的
            mm=min(mm,min(sa[i],sa[i-1]));
            mx=max(mx,max(sa[i],sa[i-1]));
            if(mx-mm-1>=k) return 1;
            //差分后,不重叠的长度需要加一
        } else {
            mx=-INF,mm=INF;
        }
    }
    return 0;
}
int main() {
    while(~scanf("%d",&n) && n) {
        for(int i=0; i<n; ++i)
            scanf("%d",a+i);
        --n;
        for(int i=0; i<n; ++i)
            r[i]=a[i+1]-a[i]+88;//差分
        r[n]=0;
        SA(r,n+1,176);
        int l=0,r=n>>1,ans=0;
        while(l<r) {
            int mid=(l+r+1)>>1;
            if(check(mid))
                l=ans=mid;
            else
                r=mid-1;
        }
        if(l>=4)//总长度大于等于5,故差分长度大于等于4
            printf("%d\n",ans+1);
        else
            printf("%d\n",0);
    }
    return 0;
}
可重叠的k次最长重复子串(pku 3261/Luogu P2852)

题意:给定一个序列,求序列中至少出现 k 次的可重叠的最长子串。
题解:类似上一题,二分答案长度 l l ,对 height 数组分组,不小于 l l 的分在一个组内,判断是否有一个组的大小至少为 k 即可。
代码:

struct node{
    int d,i;
    bool operator<(const node &x)const{
        return d<x.d;
    }
}m[20020];
int t[20020],n,k;
bool check(int mid){
    int cnt=0;
    for(int i=2;i<=n;i++){
        if(height[i]>=mid){//大括号不可省略,否则下面的else会被认为是下面的if的。
            if(++cnt>=k-1)//n个height元素表示n+1个串的LCP至少为k
                return true;
        }else
            cnt=0;
    }
    return false;
} 
int main(void)
{
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++)
        scanf("%d",&m[i].d),m[i].i=i;
    sort(m+1,m+n+1);//数据范围过大,离散化
    int cnt=0,last=0;
    for(int i=1;i<=n;i++){
        if(m[i].d==last)
            t[m[i].i]=cnt;
        else
            last=m[i].d,t[m[i].i]=++cnt;
    }
    da(t,n,20010);
    int l=1,r=n,ans=0;
    while(l<=r){//二分答案
        int mid=(l+r)>>1;
        if(check(mid))
            l=(ans=mid)+1;
        else
            r=mid-1;
    }
    printf("%d",ans);
    return 0;
}
子串个数
不相同的子串的个数(SPOJ 694/Luogu SP694/SPOJ 705/Luogu SP705)

题意:给定一个字符串,求不相同的子串的个数。
题解:只考虑后缀的前缀,则排第 k k 名的后缀有 nsa[k]+1 个前缀,但其中有 height[k] h e i g h t [ k ] 个前缀和上一个前缀相等,故有 nsa[k]+1height[k] n − s a [ k ] + 1 − h e i g h t [ k ] 个子串。
代码:

int T;
char s[maxn];
int main(void){
    scanf("%d",&T);
    while(T--){
        scanf("%s",s+1);
        const int l=strlen(s+1);
        da(s,l,127);
        int ans=0;
        for(int i=1;i<=l;i++)
            ans+=l-sa[i]+1-height[i];
        printf("%d\n",ans);
    }
    return 0;
}
回文子串
最长回文子串(ural1297/Vjudge)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值