简介
把字符串的后缀提出来,然后按照字典序排序,sa[i]表示排名为i的后缀
这样可以利用单调性等快速做很多操作。
操作
- 重要数组:SA和height
- ST表:用于O(1)查询两个后缀的最长公共前缀
常见操作技巧
- 分组:对于每一个height值
几个巧妙的题目
- 不重叠:直接判断后缀的值就好了,通常会分组。
- 不相同子串个数:子串总数-height之和,证明就是把每个后缀一次加入,看增加了多少个新子串。
- 回文子串:翻转拼接+枚举
- 记录一个比较巧妙的题目:
一个字符串可以由某个字符串重复而成,求最短组成串
枚举长度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]);
}