(详细)后缀数组模板及简单的入门题总结

这篇博客详细介绍了后缀数组(SA)的数据结构,并通过一系列的编程题目,如洛谷P3809、Hdu 3518等,展示了如何运用后缀数组解决求子串、最长公共前缀等相关问题。博客作者提供了每道题目的AC代码,帮助读者理解和掌握后缀数组的应用。

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

数据结构SA

打算把有意思的SA解法写下来!!!

接下来是SA数组的板子以及Height最长公共前缀
const int maxn = 2e5+5;//开总串长度,不要忘记连接符
int wa[maxn],wb[maxn],wsf[maxn],wv[maxn],sa[maxn];
int Rank[maxn],height[maxn],s[maxn];
char str1[maxn],str2[maxn];

//sa:字典序中排第i位的起始位置在str中第sa[i]  sa[1~n]为有效值
//rank:就是str第i个位置的后缀是在字典序排第几 rank[0~n-1]为有效值
//height:字典序排i和i-1的后缀的最长公共前缀  height[2~n]为有效值,第二个到最后一个
// sa数组的范围是0~n-1
 
int cmp(int *r,int a,int b,int k)
{
    return r[a]==r[b]&&r[a+k]==r[b+k];
}

void getsa(int *r,int *sa,int n,int m)//n为添加0后的总长
{
    int i,j,p,*x=wa,*y=wb,*t;
    for(i=0; i<m; i++)  wsf[i]=0;
    for(i=0; i<=n; i++)  wsf[x[i]=r[i]]++;
    for(i=1; i<m; i++)  wsf[i]+=wsf[i-1];
    for(i=n; i>=0; i--)  sa[--wsf[x[i]]]=i;
    p=1;
    j=1;
    for(; p<=n; j*=2,m=p)   //倍增的算法 
    {
        for(p=0,i=n+1-j; i<=n; i++)  y[p++]=i;
        for(i=0; i<=n; i++)  if(sa[i]>=j)  y[p++]=sa[i]-j;
        for(i=0; i<=n; i++)  wv[i]=x[y[i]];
        for(i=0; i<m; i++)  wsf[i]=0;
        for(i=0; i<=n; i++)  wsf[wv[i]]++;
        for(i=1; i<m; i++)  wsf[i]+=wsf[i-1];
        for(i=n; i>=0; i--)  sa[--wsf[wv[i]]]=y[i];
        t=x;
        x=y;
        y=t;
        x[sa[0]]=0;
        for(p=1,i=1; i<=n; i++)
            x[sa[i]]=cmp(y,sa[i-1],sa[i],j)? p-1:p++;
    }
}
void getheight(int *r,int n)//n为添加0后的总长
{
    int i,j,k=0;
    for(i=1; i<=n; i++)  Rank[sa[i]]=i;
    for(i=0; i<n; i++)
    {
        if(k)
            k--;
        else
            k=0;
        j=sa[Rank[i]-1];
        while(r[i+k]==r[j+k])
            k++;
        height[Rank[i]]=k;
    }
}

先来个裸题

洛谷P3809
题目意思:
读入一个长度为 n 的由大小写英文字母或数字组成的字符串,请把这个字符串的所有非空后缀按字典序从小到大排序,然后按顺序输出后缀的第一个字符在原串中的位置。位置编号为 1 到 n 。
就是求sa数组

AC代码

#include<bits/stdc++.h>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<string>
#include<cstdio>
#include<cstdlib> 
#define pb push_back
#define pback pop_back
#define ll long long 
using namespace std;


void read(int &x)
{
    int f=1;x=0;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
    while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
    x*=f;
}
inline void write(int x)
{
     if(x<0) putchar('-'),x=-x;
     if(x>9) write(x/10);
     putchar(x%10+'0');
}
const int maxn = 1e6+5;//开总串长度,不要忘记连接符
int wa[maxn],wb[maxn],wsf[maxn],wv[maxn],sa[maxn];
int Rank[maxn],height[maxn],s[maxn];
char str1[maxn],str2[maxn];
int cmp(int *r,int a,int b,int k)
{
    return r[a]==r[b]&&r[a+k]==r[b+k];
}
int len ;
void getsa(int *r,int *sa,int n,int m)//n为添加0后的总长
{
    int i,j,p,*x=wa,*y=wb,*t;
    for(i=0; i<m; i++)  wsf[i]=0;
    for(i=0; i<=n; i++)  wsf[x[i]=r[i]]++;
    for(i=1; i<m; i++)  wsf[i]+=wsf[i-1];
    for(i=n; i>=0; i--)  sa[--wsf[x[i]]]=i;
    p=1;
    j=1;
    for(; p<=n; j*=2,m=p)   //倍增的算法 
    {
        for(p=0,i=n+1-j; i<=n; i++)  y[p++]=i;
        for(i=0; i<=n; i++)  if(sa[i]>=j)  y[p++]=sa[i]-j;
        for(i=0; i<=n; i++)  wv[i]=x[y[i]];
        for(i=0; i<m; i++)  wsf[i]=0;
        for(i=0; i<=n; i++)  wsf[wv[i]]++;
        for(i=1; i<m; i++)  wsf[i]+=wsf[i-1];
        for(i=n; i>=0; i--)  sa[--wsf[wv[i]]]=y[i];
        t=x;
        x=y;
        y=t;
        x[sa[0]]=0;
        for(p=1,i=1; i<=n; i++)
            x[sa[i]]=cmp(y,sa[i-1],sa[i],j)? p-1:p++;
    }
    
    
}

void getheight(int *r,int n)//n为添加0后的总长
{
    int i,j,k=0;
    for(i=1; i<=n; i++)  Rank[sa[i]]=i;
    for(i=0; i<n; i++)
    {
        if(k)
            k--;
        else
            k=0;
        j=sa[Rank[i]-1];
        while(r[i+k]==r[j+k])
            k++;
        height[Rank[i]]=k;
    }
    
}
int dp[maxn][20];
void RMQ(int n)
{
	for(int i = 1; i <= n; i++)		dp[i][0] = height[i];
	for(int j = 1; (1 << j) <= n; j++)
		for(int i = 1; i + (1 << j ) -1 <= n; i++)
			dp[i][j] = min(dp[i][j-1], dp[i+ (1 << (j - 1))][j-1]);
	return ;
}
int askRMQ(int a, int b){
	int ra = Rank[a], rb = Rank[b];
	if(ra > rb)	swap(ra, rb);
	int k = 0;
	while((1 << (k + 1)) <= rb-ra) k++;
	return min(dp[ra+1][k], dp[rb - (1 << k) + 1][k]);
}

int main(){
	
	scanf("%s",str1);
	int pp = 0;
	len = strlen (str1);
	for(int i = 0; i<len; i++){
		s[pp++] = str1[i] - '0' +100;
	}
	s[pp] = 0;
	getsa(s,sa,pp,600);
	getheight(s,pp);
	for(int i = 1 ; i <=len;i++){
		printf("%d ",sa[i]+1);
	}
	return 0;
} 

后缀数组第一题:

求字符串的子串并且长度不小于m
牛客 CSL的密码
题意:大概就是给你一个长度为n的字符串!求这个字符串的子串的个数!满足一个条件!就是子串的长度至少为m!
我的理解:
不难发现某个子串也就是某个后缀的前缀!!!
那么原来的问题就是转换成求后缀之间的不同前缀的个数!如果按照suffix(sa[1]),suffix(sa[2]),suffix(sa[3]),……,suffix(sa[n])的顺序计算!!!我们会发现每一次新加入的suffix【sa【k】】,都会产生n-sa【i】个前缀!!!(有些人板子这里写法是n-sa【i】+1) 但是其中有height[k]个是和前面的字符串的前缀是相同的。所以suffix(sa[k])将“贡献”出n-sa[k]+1-height[k]个不同的子串。
这个题目求的是长度至少为m!!!所以每次的加入的suffix(sa[k])需要减去少于m个方案数
累加后便是原问题的答案。这个做法的时间复杂度为O(n)。**

AC 代码

#include<bits/stdc++.h>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<string>
#include<string>
typedef long long int ll;
using namespace std;
const int maxn = 2e5+5;//开总串长度,不要忘记连接符
int wa[maxn],wb[maxn],wsf[maxn],wv[maxn],sa[maxn];
int Rank[maxn],height[maxn],s[maxn];
char str1[maxn],str2[maxn];
int cmp(int *r,int a,int b,int k)
{
    return r[a]==r[b]&&r[a+k]==r[b+k];
}

void getsa(int *r,int *sa,int n,int m)
{
    int i,j,p,*x=wa,*y=wb,*t;
    for(i=0; i<m; i++)  wsf[i]=0;
    for(i=0; i<=n; i++)  wsf[x[i]=r[i]]++;
    for(i=1; i<m; i++)  wsf[i]+=wsf[i-1];
    for(i=n; i>=0; i--)  sa[--wsf[x[i]]]=i;
    p=1;
    j=1;
    for(; p<=n; j*=2,m=p)   //倍增的算法 
    {
        for(p=0,i=n+1-j; i<=n; i++)  y[p++]=i;
        for(i=0; i<=n; i++)  if(sa[i]>=j)  y[p++]=sa[i]-j;
        for(i=0; i<=n; i++)  wv[i]=x[y[i]];
        for(i=0; i<m; i++)  wsf[i]=0;
        for(i=0; i<=n; i++)  wsf[wv[i]]++;
        for(i=1; i<m; i++)  wsf[i]+=wsf[i-1];
        for(i=n; i>=0; i--)  sa[--wsf[wv[i]]]=y[i];
        t=x;
        x=y;
        y=t;
        x[sa[0]]=0;
        for(p=1,i=1; i<=n; i++)
            x[sa[i]]=cmp(y,sa[i-1],sa[i],j)? p-1:p++;
    }
}
void getheight(int *r,int n)//n为添加0后的总长
{
    int i,j,k=0;
    for(i=1; i<=n; i++)  Rank[sa[i]]=i;
    for(i=0; i<n; i++)
    {
        if(k)
            k--
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值