字符串基础模板

字符串基础模板

学习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 i1] = 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=1ns[i]×pni%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[i1]×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[l1]×prl+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) (Zbox)

对于 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 Zbox。算法过程中我们维护右端点最靠右的匹配段。为了方便,记作 [ l , r ] [l,r] [l,r] S [ l , r ] S[l,r] S[l,r]一定是S前缀。

计算完前 i − 1 i-1 i1 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,rl+1]

1.如果 i ≤ r ( 在 区 间 内 ) i≤r(在区间内) ir(),则有 s [ i , r ] = s [ i − l + 1 , r − l + 1 ] s[i,r]=s[i-l+1,r-l+1] s[i,r]=s[il+1,rl+1]
(1)若 z [ i − l + 1 ] < r − i + 1 z[i-l+1]<r-i+1 z[il+1]<ri+1,则 z [ i ] = z [ i − l + 1 ] z[i]=z[i-l+1] z[i]=z[il+1]

​ (2)若 z [ i − l + 1 ] ≥ r − i + 1 z[i-l+1]≥r-i+1 z[il+1]ri+1,则 z [ i ] = r − i + 1 z[i]=r-i+1 z[i]=ri+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 i1 d d d函数,维护区间 [ l , r ] [l,r] [l,r]

1.如果 i ≤ r ( 在 区 间 内 ) i≤r(在区间内) ir(), i i i的对称点为 r − i + l r-i+l ri+l
(1)若 d [ r − i + l ] < r − i + 1 d[r-i+l]<r-i+1 d[ri+l]<ri+1,则 d [ i ] = d [ r + i − l ] d[i]=d[r+i-l] d[i]=d[r+il]

​ (2)若 d [ r − i + l ] ≥ r − i + 1 d[r-i+l]≥r-i+1 d[ri+l]ri+1,则 d [ i ] = r − i + 1 d[i]=r-i+1 d[i]=ri+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=id[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) (az)对应的映射值 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,vu<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[:i1]=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,k1]=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,k1]

s [ k ] = s [ j ] s[k]=s[j] s[k]=s[j]时,直接 k ← k + 1 k←k+1 kk+1, j ← j + 1 j←j+1 jj+1,周期 k − j k-j kj继续保持

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} sisi+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;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值