字符串基础模板
学习b站董晓算法的一些总结
一、最小表示法
当字符串 S S S中选定一个位置 i i i满足 S S S[ i i i ~ n n n]+ S S S[ 1 1 1 ~ i − 1 i -1 i−1] = T =T =T,则 T T T是 S S S的循环同构串
最小表示法就是找出字符串 S S S的循环同构串中字典序最小的那一个
方法
复制一倍成链,扫描,用三个指针 i i i, j j j, k k k,其中 i i i, j j j控制匹配起始位置,指针 k k k控制匹配长度。
初始化
i
=
1
,
j
=
2
,
k
=
0
i=1,j=2,k=0
i=1,j=2,k=0,比较
S
[
i
+
k
]
S[i+k]
S[i+k]和
S
[
j
+
k
]
S[j+k]
S[j+k]是否相等。
相等
k
k
k++;前者大
i
=
i
+
k
+
1
i=i+k+1
i=i+k+1;后者大
j
=
j
+
k
+
1
j=j+k+1
j=j+k+1;若跳转后两个指针相同
j
j
j++,始终保证比较的两个字符串不同。最终答案为
m
i
n
(
i
,
j
)
min(i,j)
min(i,j)。
例题
洛谷 P1368 【模板】最小表示法
https://www.luogu.com.cn/problem/P1368
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int a[600005],n;
int get_min()
{
int i=1,j=2,k=0;
while(i<=n&&j<=n)
{
for(k=0;k<n&&a[i+k]==a[j+k];k++);
a[i+k]>a[j+k] ? i=i+k+1 : j=j+k+1;
if(i==j) j++;
}
return min(i,j);
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i];
a[i+n]=a[i];
}
int k=get_min();
for(int i=0;i<n;i++)
cout<<a[i+k]<<" ";
}
二、字符串哈希
把不同的字符串映射成不同的整数
把字符串映射成一个
p
p
p进制数字
对于一个长度为
n
n
n的字符串
s
s
s,定义
H
a
s
h
Hash
Hash函数:
∑
i
=
1
n
s
[
i
]
×
p
n
−
i
%
M
\sum_{i=1}^{n} s[i]×p^{n-i} \% M
i=1∑ns[i]×pn−i%M
其中 p p p取质数 131 131 131,M取大整数 2 64 2^{64} 264,可以将哈希函数定义为ULL,超过会自动溢出,等价于取模
1.求一个字符串的哈希值相当于求前缀和 h [ i ] = h [ i − 1 ] × p + s [ i ] , h [ 0 ] = 0 h[i]=h[i-1]×p+s[i],h[0]=0 h[i]=h[i−1]×p+s[i],h[0]=0
2.求一个字符串的子串的哈希值相当于求区间和
h
[
l
,
r
]
=
h
[
r
]
−
h
[
l
−
1
]
×
p
r
−
l
+
1
h[l,r]=h[r]-h[l-1]×p^{r-l+1}
h[l,r]=h[r]−h[l−1]×pr−l+1
时间复杂度前缀和
O
(
n
)
O(n)
O(n),区间和
O
(
1
)
O(1)
O(1)
代码
using ull=unsigned long long;
const int N=1e5+5;
const int P=131;
ull p[N],h[N];
char s[N];
int n,m;
void init() // 初始化
{
h[0]=0;
for(int i=1;i<=m;i++)
{
p[i]=p[i-1]*P;
h[i]=h[i-1]*P+s[i];
}
return h[m];
}
ull get(int l,int r) // 求区间和
{
return h[r]-h[l-1]*p[r-l+1];
}
bool substr(int l1,int r1,int l2,int r2) // 判断两个字符串是否相同
{
return get(l1,r1)==get(l2,r2);
}
例题
洛谷 P3370 【模板】字符串哈希
https://www.luogu.com.cn/problem/P3370
代码
#include<bits/stdc++.h>
using namespace std;
using ull=unsigned long long;
const int N=1e5+5;
const int P=131;
ull p[N],h[N];
char s[N];
int n,m;
ull cal()
{
h[0]=0;
for(int i=1;i<=m;i++)
{
//p[i]=p[i-1]*P;
h[i]=h[i-1]*P+s[i];
}
return h[m];
}
ull get(int l,int r)
{
return h[r]-h[l-1]*p[r-l+1];
}
bool substr(int l1,int r1,int l2,int r2)
{
return get(l1,r1)==get(l2,r2);
}
int main()
{
cin>>n;
map<ull,bool> map1;
for(int i=1;i<=n;i++)
{
scanf("%s",s+1);
m=strlen(s+1);
map1[cal()]=1;
}
cout<<map1.size();
}
三、 K M P KMP KMP算法
给定一个模式串 P P P和一个主串 S S S,求模式串 P P P在主串 S S S 中出现的位置
方法
1.取最长的相等前后缀,保证不漏解
2.通过模式串前后缀的自我匹配的长度,计算
n
e
x
t
next
next函数,给
j
j
j指针打表,失配时就跳到
n
e
x
t
[
j
]
next[j]
next[j]的位置继续匹配。
n e x t next next函数
n e x t next next函数表示模式串$t[1,i]中相等前后缀的最长长度
利用双指针: i i i扫描模式串, j j j扫描前缀
初始化 n e x t [ 1 ] = 0 next[1]=0 next[1]=0, i = 2 i=2 i=2, j = 0 j=0 j=0
i i i每次右移,判断
1.若 t [ i ] ! = t [ j + 1 ] t[i]!=t[j+1] t[i]!=t[j+1], j j j跳回到能匹配位置,即 j = n e x t [ j ] j=next[j] j=next[j],若未能找到匹配位置, j = 0 j=0 j=0。
2.若 t [ i ] = t [ j + 1 ] t[i]=t[j+1] t[i]=t[j+1], j = j + 1 j=j+1 j=j+1,指向匹配前缀末尾
3. n e x t [ i ] = j next[i]=j next[i]=j
int next[N];
char t[N];
void init()
{
next[1]=0;
for(int i=2,j=0;i<=n;i++)
{
while(j&&t[i]!=t[j+1]) j=next[j];
if(t[i]==t[j+1]) j+=1;
next[i]=j;
}
}
时间复杂度 O ( n ) O(n) O(n)
模式串与主串匹配
利用双指针: i i i扫描主串, j j j扫描模式串
初始化, i = 1 i=1 i=1, j = 0 j=0 j=0
i i i每次右移,判断
1.若 s [ i ] ! = t [ j + 1 ] s[i]!=t[j+1] s[i]!=t[j+1], j j j跳回到能匹配位置,即 j = n e x t [ j ] j=next[j] j=next[j],若未能找到匹配位置, j = 0 j=0 j=0。
2.若 s [ i ] = t [ j + 1 ] s[i]=t[j+1] s[i]=t[j+1], j = j + 1 j=j+1 j=j+1,指向匹配前缀末尾
3.匹配成功,输出匹配位置
void solve()
{
for(int i=1,j=0;i<=m;i++)
{
while(j&&s[i]!=t[j+1]) j=ne[j];
if(s[i]==t[j+1]) j+=1;
if(j==n) printf("%d\n",i-n+1); // 输出匹配位置
}
}
例题
洛谷 P3375 【模板】KMP字符串匹配
https://www.luogu.com.cn/problem/P3375
代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int ne[N];
char t[N];
char s[N];
int n,m; // n为模式串长度,m为主串长度
void init()
{
ne[1]=0;
for(int i=2,j=0;i<=n;i++)
{
while(j&&t[i]!=t[j+1]) j=ne[j];
if(t[i]==t[j+1]) j+=1;
ne[i]=j;
}
}
void solve()
{
for(int i=1,j=0;i<=m;i++)
{
while(j&&s[i]!=t[j+1]) j=ne[j];
if(s[i]==t[j+1]) j+=1;
if(j==n) printf("%d\n",i-n+1); // 输出匹配位置
}
}
int main()
{
scanf("%s%s",s+1,t+1);
m=strlen(s+1);n=strlen(t+1);
init();
solve();
for(int i=1;i<=n;i++)
{
printf("%d ",ne[i]);
}
}
四、扩展 K M P KMP KMP( Z Z Z函数)
对于一个长度为 n n n的字符串 S S S。 z [ i ] z[i] z[i]表示 S S S与其后缀 S [ 1 , n ] S[1,n] S[1,n]的最长公共前缀 ( L C P ) (LCP) (LCP)的长度
方法 ( Z − b o x ) (Z-box) (Z−box)
对于 i i i,我们称区间 [ i , i + z [ i ] − 1 ] [i,i+z[i]-1] [i,i+z[i]−1]是 i i i的匹配段,也可以叫 Z − b o x Z-box Z−box。算法过程中我们维护右端点最靠右的匹配段。为了方便,记作 [ l , r ] [l,r] [l,r]。 S [ l , r ] S[l,r] S[l,r]一定是S前缀。
计算完前 i − 1 i-1 i−1个 z z z函数,维护区间 [ l , r ] [l,r] [l,r],则 s [ l , r ] = s [ 1 , r − l + 1 ] s[l,r]=s[1,r-l+1] s[l,r]=s[1,r−l+1]
1.如果
i
≤
r
(
在
区
间
内
)
i≤r(在区间内)
i≤r(在区间内),则有
s
[
i
,
r
]
=
s
[
i
−
l
+
1
,
r
−
l
+
1
]
s[i,r]=s[i-l+1,r-l+1]
s[i,r]=s[i−l+1,r−l+1]
(1)若
z
[
i
−
l
+
1
]
<
r
−
i
+
1
z[i-l+1]<r-i+1
z[i−l+1]<r−i+1,则
z
[
i
]
=
z
[
i
−
l
+
1
]
z[i]=z[i-l+1]
z[i]=z[i−l+1]。
(2)若 z [ i − l + 1 ] ≥ r − i + 1 z[i-l+1]≥r-i+1 z[i−l+1]≥r−i+1,则 z [ i ] = r − i + 1 z[i]=r-i+1 z[i]=r−i+1,从 r + 1 r+1 r+1往后暴力枚举。
2.如果 i > r i>r i>r(在区间外),则从 i i i开始暴力枚举。
3.求出 z [ i ] z[i] z[i]后,如果 i + z [ i ] − 1 > r i+z[i]-1>r i+z[i]−1>r,则更新盒子 l = i l=i l=i, r = i + z [ i ] − 1 r=i+z[i]-1 r=i+z[i]−1。
int n;
char s[N];
void get_z(char &s,int n)
{
z[1]=n;
for(int i=1,l,r=0;i<=n;i++)
{
if(i<=r) z[i]=min(z[i-l+1],r-i+1);
while(s[1+z[i]]==s[i+z[i]]) z[i]++;
if(i+z[i]-1>r) l=i,r=i+z[i]-1;
}
}
时间复杂度 O ( n ) O(n) O(n)
例题
洛谷 P5410 【模板】扩展 KMP(Z 函数)
https://www.luogu.com.cn/problem/P5410
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e7+5;
int n,m;
char s[N],t[N];
int z[N],p[N];
void get_z(char *s,int n)
{
z[1]=n;
for(int i=2,l,r=0;i<=n;i++)
{
if(i<=r) z[i]=min(z[i-l+1],r-i+1);
while(s[1+z[i]]==s[i+z[i]]) z[i]++;
if(i+z[i]-1>r) l=i,r=i+z[i]-1;
}
}
void get_p(char *s,int n,char *t,int m)
{
for(int i=1,l,r=0;i<=m;i++)
{
if(i<=r) p[i]=min(z[i-l+1],r-i+1);
while(1+p[i]<=n&&i+p[i]<=m&&s[1+p[i]]==t[i+p[i]]) p[i]++;
if(i+p[i]-1>r) l=i,r=i+p[i]-1;
}
}
int main()
{
scanf("%s%s",t+1,s+1);
n=strlen(s+1);
m=strlen(t+1);
get_z(s,n);
get_p(s,n,t,m);
ll sum=0;
for(int i=1;i<=n;i++)
sum^=1ll*i*(z[i]+1);
printf("%lld\n",sum);
sum=0;
for(int i=1;i<=m;i++)
sum^=1ll*i*(p[i]+1);
printf("%lld\n",sum);
}
五、 M a n a c h e r Manacher Manacher(马拉车)
M a n a c h e r Manacher Manacher算法可以在 O ( n ) O(n) O(n)的时间内求出一个字符串中的最长回文串
改造字符串,在字符之间和串两端插入KaTeX parse error: Expected 'EOF', got '#' at position 1: #̲,改造后,都变成奇回文串,方便统一处理, s [ 0 ] = s[0]= s[0]=$(边界)
scanf("%s",a+1);
int n=strlen(a+1),k=0;
s[0]='$',s[++k]='#';
for(int i=1;i<=n;i++)
s[++k]=a[i],s[++k]='#';
n=k;
回文半径,以 i i i为中心的最长回文串的长度的一半
加速区间,算法过程中维护右端点最靠右的最长回文串,利用区间,借助之前的状态来加速新的状态。区间内 d [ i ] d[i] d[i]可以利用对称点的 d d d值转移,区间外暴力。
计算完前 i − 1 i-1 i−1个 d d d函数,维护区间 [ l , r ] [l,r] [l,r]
1.如果
i
≤
r
(
在
区
间
内
)
i≤r(在区间内)
i≤r(在区间内),
i
i
i的对称点为
r
−
i
+
l
r-i+l
r−i+l
(1)若
d
[
r
−
i
+
l
]
<
r
−
i
+
1
d[r-i+l]<r-i+1
d[r−i+l]<r−i+1,则
d
[
i
]
=
d
[
r
+
i
−
l
]
d[i]=d[r+i-l]
d[i]=d[r+i−l]。
(2)若 d [ r − i + l ] ≥ r − i + 1 d[r-i+l]≥r-i+1 d[r−i+l]≥r−i+1,则 d [ i ] = r − i + 1 d[i]=r-i+1 d[i]=r−i+1,从 r + 1 r+1 r+1往后暴力枚举。
2.如果 i > r i>r i>r(在区间外),则从 i i i开始暴力枚举。
3.求出 d [ i ] d[i] d[i]后,如果 i + d [ i ] − 1 > r i+d[i]-1>r i+d[i]−1>r,则更新盒子 l = i − d [ i ] + 1 l=i-d[i]+1 l=i−d[i]+1, r = i + d [ i ] − 1 r=i+d[i]-1 r=i+d[i]−1。
void get_d(char*s,int n){
d[1]=1;
for(int i=2,l,r=1;i<=n;i++){
if(i<=r)d[i]=min(d[r-i+l],r-i+1);
while(s[i-d[i]]==s[i+d[i]])d[i]++;
if(i+d[i]-1>r)l=i-d[i]+1,r=i+d[i]-1;
}
}
例题
洛谷 P3805 【模板】manacher 算法
https://www.luogu.com.cn/problem/P3805
代码
#include <bits/stdc++.h>
using namespace std;
const int N=2e7;
char a[N],s[N];
int d[N]; //回文半径函数
void get_d(char*s,int n){
d[1]=1;
for(int i=2,l,r=1;i<=n;i++){
if(i<=r)d[i]=min(d[r-i+l],r-i+1);
while(s[i-d[i]]==s[i+d[i]])d[i]++;
if(i+d[i]-1>r)l=i-d[i]+1,r=i+d[i]-1;
}
}
int main(){
//改造串
scanf("%s",a+1);
int n=strlen(a+1),k=0;
s[0]='$',s[++k]='#';
for(int i=1;i<=n;i++)
s[++k]=a[i],s[++k]='#';
n=k;
get_d(s,n);//计算d函数
int ans=0;
for(int i=1;i<=n;i++)
ans=max(ans,d[i]);
printf("%d\n",ans-1);
return 0;
}
六、字典树( t r i e trie trie)
T r i e Trie Trie是一种能够快速插入和查询字符串的多叉树结构。结点的编号各不相同,跟结点编号为 0 0 0,其他结点用来标识路径,还可以标记单词插入的次数。树的边表示字符。
T r i e Trie Trie维护字符串的集合,支持两种操作
1.向集合中插入字符串
2.在集合中查询一个字符串
建字典树
儿子数组 c h [ p ] ch[p] ch[p] [ j ] [j] [j]存储从结点 p p p沿着 j j j这条边走到的子节点。边为 26 26 26个小写字母 ( a − z ) (a-z) (a−z)对应的映射值 0 0 0~ 25 25 25,每个结点最多可以有 26 26 26个分叉。
计算数组 c n t [ p ] cnt[p] cnt[p]存储以结点 p p p结尾的单词的插入次数。
结点编号 i d x idx idx用来给节点编号。
1.空 T r i e Trie Trie仅有一个根节点,编号为 0 0 0
2.从根开始插入,枚举字符串的每个字符,如果有儿子结点,则 p p p指针走到儿子结点,如果没儿子结点,则先创建儿子结点, p p p指针再走到儿子结点。
3.在单词结束点记录插入次数。
char s[N];
int ch[N][26], cnt[N], idx;
void insert(char *s){
int p = 0;
for(int i = 0; s[i]; ++ i){
int j=s[i] - 'a';
if(!ch[p][j]) ch[p][j] = ++idx;
p = ch[p][j];
}
cnt[p]++;
}
查询
1.从根开始查,扫描字符串。
2.有字母 s [ i ] s[i] s[i],则走下来,能走到词尾,则返回插入次数
3.无字母 s [ i ] s[i] s[i],则返回 0 0 0
int query(char *s){
int p = 0;
for(int i = 0; s[i]; ++ i){
int j = s[i] - 'a';
if(!ch[p][j]) return 0;
p = ch[p][j];
}
return cnt[p];
}
例题
洛谷 P8306 【模板】字典树
https://www.luogu.com.cn/problem/P8306
代码
#include <bits/stdc++.h>
using namespace std;
const int N=3e6+5;
int n,q;
char s[N];
int ch[N][65],cnt[N],idx;
int getnum(char x)
{
if(x>='A'&&x<='Z')
return x-'A';
else if(x>='a'&&x<='z')
return x-'a'+26;
else
return x-'0'+52;
}
void insert(char *s)
{
int p=0;
for(int i=0; s[i]; i ++){
int j=getnum(s[i]);//字母映射
if(!ch[p][j])ch[p][j]=++idx;
p=ch[p][j];
cnt[p]++;//插入次数
}
}
int query(char *s){
int p=0;
for(int i=0; s[i]; i++){
int j=getnum(s[i]);
if(!ch[p][j]) return 0;
p=ch[p][j];
}
return cnt[p];
}
int main(){
int T;
cin>>T;
while(T--)
{
for(int i=0;i<=idx;i++)
for(int j=0;j<=65;j++)
ch[i][j]=0;
for(int i=0;i<=idx;i++)
cnt[i]=0;
idx=0;
scanf("%d%d",&n,&q);
for(int i=1;i<=n;i++){
scanf("%s",s);
insert(s);
}
for(int i=1;i<=q;i++){
scanf("%s",s);
printf("%d\n",query(s));
}
}
return 0;
}
洛谷 P4551 最长异或路径
https://www.luogu.com.cn/problem/P4551
// 洛谷 P4551 最长异或路径
// 思路:i~j的异或和=(i~1的异或和)^(j~1的异或和)
#include <bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int n;
struct edge
{
int v,w,ne;
}e[N<<1];
int head[N],tot;
int sum[N];//sum[v]存v到根的异或和
int ch[N*31][2],cnt;
void add(int u,int v,int w){
e[++tot].v=v;
e[tot].w=w;
e[tot].ne=head[u];
head[u]=tot;
}
void dfs(int u,int fa){
for(int i=head[u];i;i=e[i].ne){
int v=e[i].v,w=e[i].w;
if(v==fa)continue;
sum[v]=sum[u]^w;
dfs(v,u);
}
}
void insert(int x){
int p=0;
for(int i=30;i>=0;i--){
int t=x>>i&1;
if(!ch[p][t]) ch[p][t]=++cnt;
p=ch[p][t];
}
}
int query(int x){
int p=0,res=0;
for(int i=30;i>=0;i--){
int t=x>>i&1;
if(ch[p][!t]){
res+=1<<i;
p=ch[p][!t];
}
else p=ch[p][t];
}
return res;
}
int main(){
scanf("%d",&n);
for(int i=1;i<n;i++){
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
add(u,v,w),add(v,u,w);
}
dfs(1,0);
for(int i=1;i<=n;i++)
insert(sum[i]);
int ans=0;
for(int i=1;i<=n;i++)
ans=max(ans,query(sum[i]));
printf("%d\n",ans);
return 0;
}
七、 L y n d o n Lyndon Lyndon分解
L y n d o n Lyndon Lyndon串: S 1... ∣ S ∣ S_{1...|S|} S1...∣S∣的所有后缀中, S 1 . . . n S_1...n S1...n是一话个最小的后缀,则称 S S S为一个 L y n d o n Lyndon Lyndon串
L y n d o n Lyndon Lyndon分解:将一个字符串 S S S分解为 S 1 + S 2 + . . . + S k S_{1}+S_{2}+...+S_{k} S1+S2+...+Sk,且 S 1 > = S 2 > = . . . > = S k S_{1}>=S_{2}>=...>=S_{k} S1>=S2>=...>=Sk,且 S 1 > = S 2 > = . . . > = S k S_{1}>=S_{2}>=...>=S_{k} S1>=S2>=...>=Sk , S i S_{i} Si为 L y n d o n Lyndon Lyndon串
性质
1.有两个 L y n d o n Lyndon Lyndon串 u , v ∣ u < v {u,v|u<v} u,v∣u<v ,则 u + v u+v u+v也为一个 L y n d o n Lyndon Lyndon串
2.若存在字符串 v v v,以及字符 c c c , 满足 v + c v+c v+c为某个 L y n d o n Lyndon Lyndon串的前缀,则有 d > c d>c d>c, v + d v+d v+d为一个 L y n d o n Lyndon Lyndon串
3. L y n d o n Lyndon Lyndon分解对于一个串是唯一的
Duval 算法
时间复杂度O(n)
维护三个指针 i i i, j j j, k k k,往右依次求出 L y n d o n Lyndon Lyndon分解
s [ : i − 1 ] = s 1 s 2 . . . s m s[:i-1]=s_{1}s_{2}...s_{m} s[:i−1]=s1s2...sm是固定下来的分解,也就是 ∀ l ∈ [ 1 , m ] \forall l\in[1,m] ∀l∈[1,m], s l s_{l} sl是 L y n d o n Lyndon Lyndon串且 s l > s l + 1 s_{l}>s_{l+1} sl>sl+1
s [ i , k − 1 ] = t h s[i,k-1]=t^{h} s[i,k−1]=th是没有固定的分解,满足 t t t 是 L y n d o n Lyndon Lyndon 串,且 v v v 是 t t t 的可为空的不等于 t t t的前缀,且有 s g > s [ i , k − 1 ] s_{g}>s[i,k-1] sg>s[i,k−1]
当 s [ k ] = s [ j ] s[k]=s[j] s[k]=s[j]时,直接 k ← k + 1 k←k+1 k←k+1, j ← j + 1 j←j+1 j←j+1,周期 k − j k-j k−j继续保持
当 s [ k ] > s [ j ] s[k]>s[j] s[k]>s[j]时,由性质2 可知 v + s [ k ] v+s[k] v+s[k] 是 L y n d o n Lyndon Lyndon 串,由于 L y n d o n Lyndon Lyndon 分解需要满足 s i ≥ s i + 1 s_{i}≥s_{i+1} si≥si+1,所以不断向前合并,最终整个 t h + v + s [ k ] t^{h}+v+s[k] th+v+s[k]形成了一个新的 L y n d o n Lyndon Lyndon 串。
当 s [ k ] < s [ j ] s[k]<s[j] s[k]<s[j]时, t h t^{h} th的分解被固定下来,算法从 v v v的开头处重新开始。
例题
洛谷 P6114 【模板】Lyndon 分解
https://www.luogu.com.cn/problem/P6114
#include<bits/stdc++.h>
const int N = 5e6+5;
using namespace std;
int n,ans;
char s[N];
int main(){
scanf("%s",s+1),n=strlen(s + 1);
for(int i=1;i<=n;){
int j=i,k=i+1;
while(k<=n&&s[j]<=s[k]){
if(s[j]<s[k]) j=i; // 合并为一整个
else j++; // 保持循环不变式
k++;
}
while(i<=j){ //从v的开头重新开始
ans=ans^(i+k-j-1);
i=i+k-j;
}
}
cout<<ans<<endl;
}
八、后缀数组
把 s s s的每个后缀按照字典序排序,后缀数组 s a [ i ] sa[i] sa[i]就表示排名为 i i i的后缀的起始位置的下标而它的映射数组 r k [ i ] rk[i] rk[i]就表示起始位置的下标为 i i i的后缀的排名
例题
洛谷 P3809 【模板】后缀排序
https://www.luogu.com.cn/problem/P3809
// 洛谷 P3809 【模板】后缀排序
#include <bits/stdc++.h>
#define ffor(i,n) for(int i=1;i<=n;i++)
#define pfor(i,n) for(int i=n;i>=1;i--)
#define mem(a,b) memset(a,b,sizeof a)
using namespace std;
const int N=2e6+5;
char s[N];
int n,m;//n为后缀个数, m为桶的个数
int bar[N],sup[N],cnt[N],sa[N],rk[N],height[N];
//桶数组bar[i],辅助数组sup[i],计数数组cnt[i]
void get_sa(){
//按第一个字母排序
ffor(i,n) cnt[bar[i]=s[i]]++;
ffor(i,m) cnt[i]+=cnt[i-1];
pfor(i,n) sa[cnt[bar[i]]--]=i;
for(int k=1;k<=n;k<<=1){ //logn轮
//按第二关键字排序
mem(cnt,0);
ffor(i,n) sup[i]=sa[i];
ffor(i,n) cnt[bar[sup[i]+k]]++;
ffor(i,m) cnt[i]+=cnt[i-1];
pfor(i,n) sa[cnt[bar[sup[i]+k]]--]=sup[i];
//按第一关键字排序
mem(cnt,0);
ffor(i,n) sup[i]=sa[i];
ffor(i,n) cnt[bar[sup[i]]]++;
ffor(i,m) cnt[i]+=cnt[i-1];
pfor(i,n) sa[cnt[bar[sup[i]]]--]=sup[i];
//把后缀放入桶数组
ffor(i,n) sup[i]=bar[i];
m=0;
ffor(1,n)
if(sup[sa[i]]==sup[sa[i-1]]&&sup[sa[i]+k]==sup[sa[i-1]+k])
bar[sa[i]]=m;
else bar[sa[i]]=++m;
if(m==n) break;//已排好
}
}
void get_height(){
ffor(i,n) rk[sa[i]]=i;
for(int i=1,k=0;i<=n;i++){ //枚举后缀i
if(rk[i]==1)continue;//第一名height为0
if(k) k--;//上一个后缀的height值减1
int j=sa[rk[i]-1];//找出后缀i的前邻后缀j
while(i+k<=n&&j+k<=n&&s[i+k]==s[j+k]) k++;
height[rk[i]]=k;
}
}
int main(){
scanf("%s",s+1);
n=strlen(s+1); m=150;
get_sa();
get_height();
for(int i=1;i<=n;i++)printf("%d ",sa[i]);
return 0;
}