学习后缀数组参考这两个博客https://www.cnblogs.com/Sparks-Pion/p/9558888.html
https://www.cnblogs.com/GDOI2018/p/10292378.html
看不懂没关系,背个板子天下无敌 23333
#include <bits/stdc++.h>
using namespace std;
const int N=1e6 + 5;
int n,m,rak[N],sa[N],buc[N],id[N],height[N];
char s[N];
/*------------------------------------------------------------
rank[i] 第i个后缀的排名;
sa[i] 排名为i的后缀位置;
buc[i] 计数排序辅助数`
组;
height[i] 排名为i的后缀与排名为(i-1)的后缀的LCP;
id[i] 倍增中后半段字符串位置(计数排序中的第二关键字);
s为原串
------------------------------------------------------------*/
inline void qsort(){
//rak第一关键字,id第二关键字。
for(int i=0;i<=m;++i) buc[i]=0;
for(int i=1;i<=n;++i) ++buc[rak[id[i]]];
for(int i=1;i<=m;++i) buc[i]+=buc[i-1];
for(int i=n;i>=1;--i) sa[buc[rak[id[i]]]--]=id[i];
//计数排序,把新的二元组排序
}
//通过二元组两个下标的比较,确定两个子串是否相同
inline int cmp(int x,int y,int l){return id[x]==id[y]&&id[x+l]==id[y+l];}
int main() {
scanf("%s",s+1);
n=strlen(s+1);
for(int i=1;i<=n;++i) rak[i]=s[i],id[i]=i;
m=127,qsort();//一开始是以单个字符为单位,所以(m = 127)
for(int l=1,p=1,i;p<n;l<<=1,m=p){
//l 当前一个子串的长度; m 当前离散后的排名种类数
//当前的id(第二关键字)可直接由上一次的sa的得到
//更新sa值,并用id暂时存下上一轮的rak(用于cmp比较)
for(p=0,i=n-l+1;i<=n;++i) id[++p]=i;//长度越界,第二关键字为0
for(i=1;i<=n;++i) if (sa[i]>l) id[++p]=sa[i]-l;
qsort(),swap(rak,id),rak[sa[1]]=p=1;
//用已经完成的SA来更新与它互逆的rak,并离散rak
for(i=2;i<=n;++i) rak[sa[i]]=cmp(sa[i],sa[i-1],l)?p:++p;
}
for(int i = 1; i <= n; i ++)cout << sa[i] << " ";
cout << endl;
//LCP 这个知道原理后就比较好理解程序
int j,k=0;
for(int i=1;i<=n;height[rak[i++]]=k)
for(k=k?k-1:k,j=sa[rak[i]-1];s[i+k]==s[j+k];++k);
return 0;
}
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef long double ld;
const ll maxn = 1e6 + 5;
const ll mod = 1e9 + 7;
ll n,m,rak[maxn],id[maxn],sa[maxn],buc[maxn],height[maxn];
char s[maxn];
void qsort()
{
for(int i = 0; i <= m; i ++)buc[i] = 0;
for(int i = 1; i <= n; i ++)++buc[rak[id[i]]];
for(int i = 1; i <= m; i ++)buc[i] += buc[i - 1];
for(int i = n; i >= 1; i --)sa[buc[rak[id[i]]]--] = id[i];
}
ll cmp(ll x,ll y,ll l)
{
return id[x] == id[y] && id[x + l] == id[y + l];
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin >> (s + 1);
n = strlen(s + 1);
for(int i = 1; i <= n; i ++)rak[i] = s[i],id[i] = i;
m = 127,qsort();
for(int l = 1, p = 1,i; p < n; l <<= 1, m = p)
{
for(p = 0,i = n - l + 1; i <= n; i ++)id[++p] = i;
for(i = 1; i <= n; i ++)if(sa[i] > l)id[++p] = sa[i] - l;
qsort(),swap(rak,id),rak[sa[1]] = p = 1;
for(i = 2; i <= n; i ++)rak[sa[i]] = cmp(sa[i],sa[i - 1],l) ? p : ++p;
}
for(int i = 1; i <= n; i ++)cout << sa[i] << " ";
cout << endl;
ll k = 0;
for(int i = 1; i <= n; i ++)
{
if(k)k--;
ll j = sa[rak[i] - 1];
while(s[i + k] == s[j + k])k++;
height[rak[i]] = k;
}
return 0;
}
后缀数组应用
两个后缀的最大公共前缀
lcp(x,y)=min(heigh[x−y])lcp(x,y)=min(heigh[x−y]), 用rmq维护,O(1)查询
可重叠最长重复子串
Height数组里的最大值
不可重叠最长重复子串 POJ1743
首先二分答案xx,对height数组进行分组,保证每一组的minheightminheight都>=x>=x
依次枚举每一组,记录下最大和最小长度,多sa[mx]−sa[mi]>=xsa[mx]−sa[mi]>=x那么可以更新答案
本质不同的子串的数量
枚举每一个后缀,第ii个后缀对答案的贡献为len−sa[i]+1−height[i]