【暖*墟】#后缀数组# 后缀数组学习与练习

后缀数组的概念及模板

(1)基础概念和变量设定

n:字符串长度;m:字符种类总数(一般设置为127)。

sa[i]:排名为i的后缀的位置。

rank[i]:从第i个位置开始的后缀(后缀i)的排名。

  • 其中sa和rank的关系为:rank[sa[i]]=i,sa[rank[i]]=i。

tp[i]:基数排序的第二关键字。即第二关键字排名为i的后缀的位置。

tax[i]:i号元素出现了多少次。辅助基数排序。

s:字符串,s[i]表示字符串中第i个字符。

lcp(x,y):此处指x号后缀与y号后缀的最长公共前缀长度。

height[i]:lcp(sa[i],sa[i−1])。注意是sa数组。

即:排名为i的后缀与排名为i−1的后缀的最长公共前缀。

H[i]:height[rank[i]],即i号后缀与它前一名的后缀的最长公共前缀。

  • 性质:1.H[i]⩾H[i−1]−1;2.k个连续公共前缀排名接近,在sa[]中连续出现。

(2)模板代码实现

const int maxn=500019; int n,m; char s[maxn];

int rank[maxn],b[maxn],sa[maxn],tp[maxn],tax[maxn],height[maxn];

void qsort(){ //(1)基数排序
    for(int i=0;i<=m;i++) tax[i]=0;
    for(int i=1;i<=n;i++) tax[rank[i]]++;
    for(int i=1;i<=m;i++) tax[i]+=tax[i-1];
    for(int i=n;i>=1;i--) sa[tax[rank[tp[i]]]--]=tp[i];
}

void SuffixSort(){ //(2)后缀排序
    for(int i=1;i<=n;i++) rank[i]=s[i],tp[i]=i;
    m=519; qsort(); //第一次基数排序
    for(int k=1;k<=n;k<<=1){
        int p=0; for(int i=n-k+1;i<=n;i++) tp[++p]=i;
        //for(int i=1;i<=k;i++) tp[++p]=n-k+i;
        for(int i=1;i<=n;i++) if(sa[i]>k) tp[++p]=sa[i]-k;
        qsort(); // swap(rank,tp); 
        for(int i=1;i<=n;i++) //注意此处数组交换的方式
            b[i]=tp[i],tp[i]=rank[i],rank[i]=b[i]; rank[sa[1]]=p=1;
        for(int i=2;i<=n;i++)
            rank[sa[i]]=(tp[sa[i]]==tp[sa[i-1]]
                &&tp[sa[i]+k]==tp[sa[i-1]+k])?p:++p;
        if(p>=n) break; m=p;
    }
}

void getH(){ //(3)求height[]
    int k=0; for(int i=1;i<=n;i++){
        if(k) k--; int j=sa[rank[i]-1];
        while(s[i+k]==s[j+k]) k++; height[rank[i]]=k;  }
} 

//注意:此模板字符串从1位置开始,即scanf("%s",s+1);

(3)后缀数组常见模型

1.可重叠的最长重复子串问题

方法:求所有height的最大值就是答案。

2.求不可重叠的最长重复子串问题

方法:二分length转换成判定性问题,将所有height大于等于的划分成若干个段。

每段两两之间都是lcp大于等于k的。如果段中有两个元素的sa值绝对值大于等于k就符合条件。

【关于height数组的分组】

其实是把n个后缀根据算出来的height分组。

sa数组是按后缀排名的,如果几个后缀有部分公共前缀,

那么在后缀排名上一定是(字典序)挨在一起的。

对于我们考虑的不同的公共前缀(直接枚举height[i]即可),

可以把后缀排名里面挨在一起、公共前缀长度大于二分的值的,分为一组。

再判断组中是否满足有长度大于二分值的公共字串(即后缀的公共前缀)。

3.可重叠的k次最长重复子串

方法:判断是否存在一个划分出来的段的长度大于等于 k - 1 。

此处要注意,k - 1 个 height[ ] 就是 k 个后缀串。

4.求重复出现的子串数目

方法:可以考虑,每一个height值就能贡献当前值的答案;

但是这样算下来,会有的子串算重复。

考虑当前的和排名靠前一个的:如果当前的height大于前height的值,

那么就多加上了前一个的height值,减去即可。

5.不相同子串计数问题

方法:只考虑后缀的前缀,则排第 k 名的后缀有 n−sa[k]+1 个前缀,

但其中有 height[k] 个前缀和上一个前缀相等,故有 n−sa[k]+1−height[k] 个子串。

6.字典序第k小字符串问题

方法:同上一个,我们在对不相同子串进行计数的时候,

子串的字典序值是递增的,这样算到分界点,然后进行二分或者枚举。

7.连续重复子串问题

方法:首先枚举子串的长度k,如果符合题目要求 suffix(1) 和 suffix(k + 1) 的 lcp 应当是k + 1。

这个就用些操作随便处理了,可以用st表, 也可以考虑两个后缀的sa必然相邻。

8.多个字符串的相关问题

方法:将字符串头尾相连(注意是否要顺反连两次),

中间添加一个(未出现的)极大的字符,就可以进行一些常规操作了。

后缀数组的相关练习题集

#include <cmath>
#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
#include <vector>
#include <algorithm>
#include <stack>
#include <queue>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;

//【p3809】后缀数组

const int MAXN=1e6+10;

char s[MAXN]; int n,m,rank[MAXN],sa[MAXN],tax[MAXN],tp[MAXN];

void Qsort(){ //[基数排序]
    for(int i=0;i<=m;i++) tax[i]=0; //tax数组类似桶,将桶清空
    for(int i=1;i<=n;i++) tax[rank[i]]++; //元素放入桶中
    for(int i=1;i<=m;i++) tax[i]+=tax[i-1]; //求前缀(大小排名)
    for(int i=n;i>=1;i--) sa[tax[rank[tp[i]]]--]=tp[i];
}

void SuffixSort(){ //求出sa[]、rank[]
    for(int i=1;i<=n;i++) rank[i]=s[i]-'0'+1,tp[i]=i; Qsort();
    for(int w=1,p=0;p<n;m=p,w<<=1){ //w:当前倍增的长度
    //w=x表示已经求出了长度为x的后缀的排名,现在要更新长度为2x的后缀的排名

        p=0; for(int i=1;i<=w;i++) tp[++p]=n-w+i;
        for(int i=1;i<=n;i++) if(sa[i]>w) tp[++p]=sa[i]-w; 

        Qsort(); //已经更新了第二关键字,利用上一轮的rank更新本轮的sa

        std::swap(tp,rank); rank[sa[1]]=p=1;

        for(int i=2;i<=n;i++) //当两个后缀上一轮排名相同时本轮也相同
            rank[sa[i]]=(tp[sa[i-1]]==tp[sa[i]]
                &&tp[sa[i-1]+w]==tp[sa[i]+w])?p:++p;
        
    } for(int i=1;i<=n;i++) printf("%d ",sa[i]);
}

int main(){ scanf("%s",s+1); n=strlen(s+1); m=127; SuffixSort(); }
p3809【模板】后缀排序 //求sa数组
#include <cmath>
#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
#include <vector>
#include <algorithm>
#include <stack>
#include <queue>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;

/*【p4051】字符加密 */

/*【分析】把字符串接到自己后面变成长度为2∗len以后,进行后缀排序。
按题目要求输出结尾的字符。[破环为链+求sa数组] */

const int maxn=200019; int n,m; char s[maxn];

int rank[maxn],b[maxn],sa[maxn],tp[maxn],tax[maxn],height[maxn];

void qsort(){
    for(int i=0;i<=m;i++) tax[i]=0;
    for(int i=1;i<=n;i++) tax[rank[i]]++;
    for(int i=1;i<=m;i++) tax[i]+=tax[i-1];
    for(int i=n;i>=1;i--) sa[tax[rank[tp[i]]]--]=tp[i];
}

void SuffixSort(){
    for(int i=1;i<=n;i++) rank[i]=s[i],tp[i]=i;
    m=519; qsort(); //第一次基数排序
    for(int k=1;k<=n;k<<=1){
        int p=0; for(int i=n-k+1;i<=n;i++) tp[++p]=i;
        //for(int i=1;i<=k;i++) tp[++p]=n-k+i;
        for(int i=1;i<=n;i++) if(sa[i]>k) tp[++p]=sa[i]-k;
        qsort(); // swap(rank,tp); 
        for(int i=1;i<=n;i++) b[i]=tp[i],tp[i]=rank[i],rank[i]=b[i]; rank[sa[1]]=p=1;
        for(int i=2;i<=n;i++)
            rank[sa[i]]=(tp[sa[i]]==tp[sa[i-1]]&&tp[sa[i]+k]==tp[sa[i-1]+k])?p:++p;
        if(p>=n) break; m=p;
    }
}

int main(){
    scanf("%s",s+1); n=strlen(s+1);
    for(int i=1;i<=n;i++) s[i+n]=s[i]; 
    n+=n; SuffixSort(); 
    for(int i=1;i<=n;i++) if(sa[i]<=n/2) 
        putchar(s[sa[i]+n/2-1]); cout<<endl;
}
p4051 字符加密 //拆环为链 + 求sa数组
#include <cmath>
#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
#include <vector>
#include <algorithm>
#include <stack>
#include <queue>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;

/*【SP694】disubstr [不相同子串个数]
给定一个字符串,求不相同的子串的个数。*/

/*【分析】只考虑后缀的前缀,则排第 k 名的后缀有 n−sa[k]+1 个前缀,
但其中有 height[k] 个前缀和上一个前缀相等,故有 n−sa[k]+1−height[k] 个子串。 */

const int maxn=50019; int n,m; char s[maxn];

int rank[maxn],b[maxn],sa[maxn],tp[maxn],tax[maxn],height[maxn];

void qsort(){
    for(int i=0;i<=m;i++) tax[i]=0;
    for(int i=1;i<=n;i++) tax[rank[i]]++;
    for(int i=1;i<=m;i++) tax[i]+=tax[i-1];
    for(int i=n;i>=1;i--) sa[tax[rank[tp[i]]]--]=tp[i];
}

void SuffixSort(){
    for(int i=1;i<=n;i++) rank[i]=s[i]-'A'+19,tp[i]=i;
    m=519; qsort(); //第一次基数排序
    for(int k=1;k<=n;k<<=1){
        int p=0; for(int i=n-k+1;i<=n;i++) tp[++p]=i;
        //for(int i=1;i<=k;i++) tp[++p]=n-k+i;
        for(int i=1;i<=n;i++) if(sa[i]>k) tp[++p]=sa[i]-k;
        qsort(); // swap(rank,tp); 
        for(int i=1;i<=n;i++) b[i]=tp[i],tp[i]=rank[i],rank[i]=b[i]; rank[sa[1]]=p=1;
        for(int i=2;i<=n;i++)
            rank[sa[i]]=(tp[sa[i]]==tp[sa[i-1]]&&tp[sa[i]+k]==tp[sa[i-1]+k])?p:++p;
        if(p>=n) break; m=p;
    }
}

void getH(){ //height[]
    int k=0; for(int i=1;i<=n;i++){
        if(k) k--; int j=sa[rank[i]-1];
        while(s[i+k]==s[j+k]) k++; height[rank[i]]=k;
    }
}

int main(){
    int T; scanf("%d",&T); 
    while(T--){
        scanf("%s",s+1); n=strlen(s+1);
        SuffixSort(); getH();
        int ans=0; for(int i=1;i<=n;i++)
            ans+=n-sa[i]+1-height[i];
        printf("%d
",ans);
    }
}
SP694 disubstr //计数:不相同子串个数
#include <cmath>
#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
#include <vector>
#include <algorithm>
#include <stack>
#include <queue>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;

/*【p2743】乐曲主题 [不可重叠最长重复子串]
给定一个序列,求等价的最长子串,且长度大于5,不可重叠。
等价的定义:对序列整体加上某个数值后与另一个序列完全相等。 */

/*【分析】原长度为l+1的等价序列 <=> 差分后长度为l的相等序列。
即:求不可重叠的最长重复子串。二分答案k,问题转换为判定是否存在长度为k的最长子串。*/

int read(){ int x=0,f=1;char ss=getchar();
  while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();}
  while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();}return x*f;} 

const int maxn=50019;

int n,m,a[maxn],rank[maxn],sa[maxn],tp[maxn],tax[maxn],height[maxn];

void qsort(){
    for(int i=0;i<=m;i++) tax[i]=0;
    for(int i=1;i<=n;i++) tax[rank[i]]++;
    for(int i=1;i<=m;i++) tax[i]+=tax[i-1];
    for(int i=n;i>=1;i--) sa[tax[rank[tp[i]]]--]=tp[i];
}

void SuffixSort(){
    for(int i=1;i<=n;i++) rank[i]=a[i],tp[i]=i;
    m=519; qsort(); //第一次基数排序
    for(int k=1;k<=n;k<<=1){
        int p=0; for(int i=n-k+1;i<=n;i++) tp[++p]=i;
        //for(int i=1;i<=k;i++) tp[++p]=n-k+i;
        for(int i=1;i<=n;i++) if(sa[i]>k) tp[++p]=sa[i]-k;
        qsort(); swap(rank,tp); rank[sa[1]]=p=1;
        for(int i=2;i<=n;i++)
            rank[sa[i]]=(tp[sa[i]]==tp[sa[i-1]]&&tp[sa[i]+k]==tp[sa[i-1]+k])?p:++p;
        if(p>=n) break; m=p;
    }
}

void getH(){ //height[]
    int k=0; for(int i=1;i<=n;i++){
        if(k) k--; int j=sa[rank[i]-1];
        while(a[i+k]==a[j+k]) k++; height[rank[i]]=k;
    }
}

int check(int x){
    int mx=sa[1],mi=sa[1];
    for(int i=2;i<=n;i++){
        if(height[i]<x) mx=mi=sa[i];
        else{ if(sa[i]<mi) mi=sa[i];
              if(sa[i]>mx) mx=sa[i];
              if(mx-mi>x) return true; }
    } return false;
}

int main(){
    while(scanf("%d",&n)!=EOF){
        if(n==0) break;
        for(int i=1;i<=n;i++) a[i]=read();
        for(int i=1;i<n;i++) a[i]=a[i+1]-a[i]+90;
        n--; //差分数组长度少1

        SuffixSort(); getH(); //后缀排序+height数组(连续后缀的最长公共前缀)

        int ans=0,L=0,R=n,mid; //二分答案+height分组判断
        while(L<R){ mid=L+R>>1; if(check(mid)) ans=mid,L=mid+1; else R=mid; }
        if(ans<4) printf("0
"); else printf("%d
",ans+1);
    }
}
p2743 乐曲主题 //不可重叠最长重复子串 + 差分
#include <cmath>
#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
#include <vector>
#include <algorithm>
#include <stack>
#include <queue>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;

/*【p2852】牛奶模式 [可重叠的k次最长重复子串]
给定一个序列,求序列中至少出现 k 次的可重叠的最长子串*/

/*【分析】二分答案长度l,对height数组进行分组。
即:将height不小于l的放到同一组中。对于不同的height判断是否有一组大小>=k。*/

/*【关于height数组的分组】其实是把n个后缀根据算出来的height分组。
sa数组是按后缀排名的,如果几个后缀有部分公共前缀,那么在后缀排名上一定是(字典序)挨在一起的。
对于不同的公共前缀,可以把后缀排名里面[挨在一起、公共前缀长度大于二分的值]的,分为一组。
这一组里满足有长度大于二分值的公共字串(即后缀的公共前缀),再检查组内是不是有超过k个后缀。*/

const int maxn=50019; int n,m,k,s[maxn];

struct node{ int d,id; }a[maxn];

bool cmp(node a,node b){ return a.d<b.d; }

int rank[maxn],sa[maxn],tp[maxn],tax[maxn],height[maxn];

void qsort(){
    for(int i=0;i<=m;i++) tax[i]=0;
    for(int i=1;i<=n;i++) tax[rank[i]]++;
    for(int i=1;i<=m;i++) tax[i]+=tax[i-1];
    for(int i=n;i>=1;i--) sa[tax[rank[tp[i]]]--]=tp[i];
}

void SuffixSort(){
    for(int i=1;i<=n;i++) rank[i]=s[i],tp[i]=i;
    m=519; qsort(); //第一次基数排序
    for(int k=1;k<=n;k<<=1){
        int p=0; for(int i=n-k+1;i<=n;i++) tp[++p]=i;
        //for(int i=1;i<=k;i++) tp[++p]=n-k+i;
        for(int i=1;i<=n;i++) if(sa[i]>k) tp[++p]=sa[i]-k;
        qsort(); swap(rank,tp); rank[sa[1]]=p=1;
        for(int i=2;i<=n;i++)
            rank[sa[i]]=(tp[sa[i]]==tp[sa[i-1]]&&tp[sa[i]+k]==tp[sa[i-1]+k])?p:++p;
        if(p>=n) break; m=p;
    }
}

void getH(){ //height[]
    int k=0; for(int i=1;i<=n;i++){
        if(k) k--; int j=sa[rank[i]-1];
        while(s[i+k]==s[j+k]) k++; height[rank[i]]=k;
    }
}

bool check(int mid){
    int cnt=0;
    for(int i=2;i<=n;i++){
        if(height[i]<mid) cnt=0; //另外的组
        else if(++cnt>=k-1) return true;
        //k-1个height元素表示k个后缀串
    } return false;
}

int main(){
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++)  scanf("%d",&a[i].d),a[i].id=i;
    sort(a+1,a+n+1,cmp); int cnt=0,lastt=0; //离散化
    for(int i=1;i<=n;i++){ //利用排序连续性,将每个数字缩小
        if(a[i].d==lastt) s[a[i].id]=cnt;
        else lastt=a[i].d,s[a[i].id]=++cnt;
    } SuffixSort(); getH(); int l=1,r=n,ans=0,mid; 
    while(l<=r){ mid=(l+r)>>1; if(check(mid)) ans=mid,l=mid+1; else r=mid-1; }
    printf("%d
",ans); return 0; //至少出现k次的可重叠的最长子串
}
p2852 牛奶模式 //可重叠的k次最长重复子串:height分组
#include <cmath>
#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
#include <vector>
#include <algorithm>
#include <stack>
#include <queue>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;

/*【p4248】差异
求(n-1)*n*(n+1)/2−2×所有后缀的公共前缀长度lcp。*/

//【标签】后缀数组 + 数学分析 + 分情况讨论 + 单调栈维护dp

//对于每一个height[i],若height[i-1]<=height[i],
//那么height[i-1]能取到的值height[i]都能取到;

//若height[i-1]>height[i],则对于i位置来说、LCP长度就是height[i]。

//用单调栈维护距i最近且小于等于height[i]的位置p。

//那么转移方程为:f[i]=f[p]+(i-p)*height[i],ans=∑f[i]。

const int maxn=500019; int n,m; char s[maxn];

int rank[maxn],b[maxn],sa[maxn],tp[maxn],tax[maxn],height[maxn];

void qsort(){
    for(int i=0;i<=m;i++) tax[i]=0;
    for(int i=1;i<=n;i++) tax[rank[i]]++;
    for(int i=1;i<=m;i++) tax[i]+=tax[i-1];
    for(int i=n;i>=1;i--) sa[tax[rank[tp[i]]]--]=tp[i];
}

void SuffixSort(){
    for(int i=1;i<=n;i++) rank[i]=s[i],tp[i]=i;
    m=519; qsort(); //第一次基数排序
    for(int k=1;k<=n;k<<=1){
        int p=0; for(int i=n-k+1;i<=n;i++) tp[++p]=i;
        //for(int i=1;i<=k;i++) tp[++p]=n-k+i;
        for(int i=1;i<=n;i++) if(sa[i]>k) tp[++p]=sa[i]-k;
        qsort(); // swap(rank,tp); 
        for(int i=1;i<=n;i++) 
            b[i]=tp[i],tp[i]=rank[i],rank[i]=b[i]; rank[sa[1]]=p=1;
        for(int i=2;i<=n;i++)
            rank[sa[i]]=(tp[sa[i]]==tp[sa[i-1]]
                &&tp[sa[i]+k]==tp[sa[i-1]+k])?p:++p;
        if(p>=n) break; m=p;
    }
}

void getH(){ 
    int k=0; //求height[]
    for(int i=1;i<=n;i++){
        if(k) k--; int j=sa[rank[i]-1];
        while(s[i+k]==s[j+k]) k++; height[rank[i]]=k; 
    }
}

struct node{ int val,pos; }; stack <node> sta; ll f[maxn];

int main(){
    scanf("%s",s+1),n=strlen(s+1);
    SuffixSort(); getH(); ll ans=0;
    for(int i=1;i<=n;i++){ int p=0;
        while(!sta.empty()&&sta.top().val>height[i]) sta.pop();
        if(!sta.empty()) p=sta.top().pos;
        f[i]=f[p]+(i-p)*height[i],ans+=f[i];
        sta.push((node){height[i],i});
    } printf("%lld
",(ll)(n-1)*n*(n+1)/2-2*ans);
}
p4248 差异 //求∑后缀lcp:数学分析 + 单调栈维护dp
#include <cmath>
#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
#include <vector>
#include <algorithm>
#include <stack>
#include <queue>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;

/*【p3181】找相同字符 求相同子串个数
求出在两个字符串中各取出一个子串使得这两个子串相同的方案数。*/

//通过一个无用字符拼接两个字符串,求height[],可以求出A、B相同子串。

//找到所有极大的公共串

const int maxn=500019; int n,m,len1,len2; char s[maxn],ss[maxn];

int rank[maxn],b[maxn],sa[maxn],tp[maxn],tax[maxn],height[maxn];

void qsort(){
    for(int i=0;i<=m;i++) tax[i]=0;
    for(int i=1;i<=n;i++) tax[rank[i]]++;
    for(int i=1;i<=m;i++) tax[i]+=tax[i-1];
    for(int i=n;i>=1;i--) sa[tax[rank[tp[i]]]--]=tp[i];
}

void SuffixSort(){
    for(int i=1;i<=n;i++) rank[i]=s[i],tp[i]=i;
    m=519; qsort(); //第一次基数排序
    for(int k=1;k<=n;k<<=1){
        int p=0; for(int i=n-k+1;i<=n;i++) tp[++p]=i;
        //for(int i=1;i<=k;i++) tp[++p]=n-k+i;
        for(int i=1;i<=n;i++) if(sa[i]>k) tp[++p]=sa[i]-k;
        qsort(); // swap(rank,tp); 
        for(int i=1;i<=n;i++) 
            b[i]=tp[i],tp[i]=rank[i],rank[i]=b[i]; rank[sa[1]]=p=1;
        for(int i=2;i<=n;i++)
            rank[sa[i]]=(tp[sa[i]]==tp[sa[i-1]]
                &&tp[sa[i]+k]==tp[sa[i-1]+k])?p:++p;
        if(p>=n) break; m=p;
    }
}

void getH(){ 
    int k=0; //求height[]
    for(int i=1;i<=n;i++){
        if(k) k--; int j=sa[rank[i]-1];
        while(s[i+k]==s[j+k]) k++; height[rank[i]]=k; 
    }
}

ll sum[maxn],sta[maxn],now[maxn],top=0,ans=0;

void work(){ //找出所有在A、B中的后缀串,分类加减
    for(int i=1;i<=n;i++) sum[i]=sum[i-1]+(sa[i]<=len1);
    top=0,sta[0]=1; for(int i=1;i<=n;i++){
        while(top>0&&height[sta[top]]>height[i]) top--; sta[++top]=i; 
        now[top]=(ll)(sum[i-1]-sum[sta[top-1]-1])*height[i]+now[top-1];
        if(sa[i]>len1+1) ans+=now[top];
    } top=0; for(int i=1;i<=n;i++) sum[i]=sum[i-1]+(sa[i]>len1+1);
    for(int i=1;i<=n;i++){ //top=0相当于清空栈,now[0]一直=0,所以不用清零
        while(top>0&&height[sta[top]]>height[i]) top--; sta[++top]=i; 
        now[top]=(ll)(sum[i-1]-sum[sta[top-1]-1])*height[i]+now[top-1];
        if(sa[i]<=len1) ans+=now[top];
    }
}

int main(){
    scanf("%s",s+1),n=strlen(s+1),len1=n,s[++n]=127;
    scanf("%s",ss+1),len2=strlen(ss+1);
    for(int i=1;i<=len2;i++) s[++n]=ss[i];
    SuffixSort(); getH(); work(); cout<<ans<<endl;
}
p3181 找相同字符 //求相同子串个数:两串拼接 + 单调栈维护分类计数

p.s.本zz忽然发现还有一个月就要省选了,而我还在...咕咕咕0v0...真可怕...

                        ——时间划过风的轨迹,那个少年,还在等你

原文地址:https://www.cnblogs.com/FloraLOVERyuuji/p/10382408.html