板子:后缀数组

后缀数组是一种数据结构,将字符串的后缀按字典序排序,可用于快速执行多种操作,如查找最长公共前缀。重要数组包括SA和height,通过ST表可实现O(1)查询。常见应用包括分组、不重叠子串和回文子串问题。题目中还介绍了一种求字符串最短组成串的方法,即枚举长度并检查后缀的最长公共前缀。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

简介

把字符串的后缀提出来,然后按照字典序排序,sa[i]表示排名为i的后缀

这样可以利用单调性等快速做很多操作。

操作

  • 重要数组:SA和height
  • ST表:用于O(1)查询两个后缀的最长公共前缀

常见操作技巧

  1. 分组:对于每一个height值

几个巧妙的题目

  1. 不重叠:直接判断后缀的值就好了,通常会分组。
  2. 不相同子串个数:子串总数-height之和,证明就是把每个后缀一次加入,看增加了多少个新子串。
  3. 回文子串:翻转拼接+枚举
  4. 记录一个比较巧妙的题目:
    一个字符串可以由某个字符串重复而成,求最短组成串
    枚举长度K,首先保证K是可以整除len,然后看suffix(1)和suffix(k+1)的最长公共前缀,如果=n-k,则代表了每一段K都可以和前面一段匹配,找到答案,其中suffix(1)不变可以直接刷

代码

注释版

char s[maxn];
int sa[maxn],t[maxn<<1],t2[maxn<<1],c[maxn],height[maxn],rank[maxn];//c必须开这么大
//s从1开始,排名从1开始
void getSa(int n,int m)//n表示字符串长度,
{
    //c数组是桶 
    //xi表示后缀i的第一关键字大小,可以重复 
    //yi表示第二关键字排名为i的后缀
    //但是在最后重新统计x的时候,y转变为后缀i的第一关键字的大小 
    int i,*x=t,*y=t2;
    for(i=1;i<=m;i++)c[i]=0;
    for(i=1;i<=n;i++)c[x[i]=s[i]]++;//c[i]表示值为i的有多少个,x[i]是后缀i的第一关键字大小 
    for(int i=2;i<=m;i++)c[i]+=c[i-1];//求一个前缀和得到<=i的有多少个
    for(i=n;i;i--)sa[c[x[i]]--]=i;//求后缀i的排名,实际上就是权值<=i的有多少个,减是为了遇到权值相同的有区分,i比较大的排名会靠后 
    for(int k=1;k<=n;k<<=1)//K是一个关键字代表的长度 
    {
        int num=0;
        for(i=n-k+1;i<=n;i++)y[++num]=i;//其中末尾没有K割字符的第二关键字最小 
        for(i=1;i<=n;i++)if(sa[i]>k)y[++num]=sa[i]-k;//sa[i]现在是对x排序的关键字,倒推出它是谁的第二关键字。 
        for(i=1;i<=m;i++)c[i]=0;
        for(i=1;i<=n;i++)c[x[i]]++;
        for(i=2;i<=m;i++)c[i]+=c[i-1];//基本和开头同理 
        for(i=n;i;i--)sa[c[x[y[i]]]--]=y[i];
        //第二关键字排名第i的后缀的排名是:<=该后缀第一关键字的后缀数量。
        //如果之后有第一关键字相同的后缀,第二关键字比当前小,排名也会减小 
        swap(x,y);
        //x数组现在没用,我们用来临时存一下东西,yi现在是第一关键字排名为i的后缀
        //处理完之后x[i]应该是后缀i按照1、2关键字排序后的排名
        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;
        //如果大小和前面那个相同,就沿用,否则+1,稍大
        //当前已经保证了后面的>=前面的 
        if(num==n)break;//如果全部不同,可以退出 
        m=num;
    }
}
void getHeight()
{
    int k=0;//k即使h 
    for(int i=1;i<=n;i++)rank[sa[i]]=i;//获取后缀i的排名 
    for(int i=1;i<=n;i++)
    {
        if(rank[i]==1)continue;//第一名为0,特判 
        if(k)k--;//h[i]>=h[i-1]-1 
        int j=sa[rank[i]-1];
        while(j+k<=n && i+k<=n && s[i+k]==s[j+k])k++;//注意边界判断 
        height[rank[i]]=k;
    }
}

无注释版

char s[maxn];
int sa[maxn],t[maxn<<1],t2[maxn<<1],c[maxn],height[maxn],rank[maxn];
void getSa(int n,int m)
{
    int i,*x=t,*y=t2;
    for(i=1;i<=m;i++)c[i]=0;
    for(i=1;i<=n;i++)c[x[i]=s[i]]++;
    for(int i=2;i<=m;i++)c[i]+=c[i-1];
    for(i=n;i;i--)sa[c[x[i]]--]=i;

    for(int k=1;k<=n;k<<=1)
    {
        int num=0;
        for(i=n-k+1;i<=n;i++)y[++num]=i;
        for(i=1;i<=n;i++)if(sa[i]>k)y[++num]=sa[i]-k;

        for(i=1;i<=m;i++)c[i]=0;
        for(i=1;i<=n;i++)c[x[i]]++;
        for(i=2;i<=m;i++)c[i]+=c[i-1];
        for(i=n;i;i--)sa[c[x[y[i]]]--]=y[i]; 

        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 getHeight()
{
    int k=0;
    for(int i=1;i<=n;i++)rank[sa[i]]=i;
    for(int i=1;i<=n;i++)
    {
        if(rank[i]==1)continue;
        if(k)k--;
        int j=sa[rank[i]-1];
        while(j+k<=n && i+k<=n && s[i+k]==s[j+k])k++;
        height[rank[i]]=k;
    }
}

ST表

int stmin[maxn][oo+5],mn[maxn];
void getST()
{
    mn[0]=-1;
    for(int i=1;i<n;i++)
    {
        mn[i]=i&(i-1)?mn[i-1]:mn[i-1]+1;
        stmin[i][0]=height[i];
    }
    for(int j=1;j<=oo;j++)
        for(int i=1;i+(1<<j)<=n;i++)
            stmin[i][j]=min(stmin[i][j-1],stmin[i+(1<<(j-1))][j-1]);
}
int query(int i,int j)
{
    int k=mn[j-i+1];
    return min(stmin[i][k],stmin[j-(1<<k)+1][k]);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值