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


    后缀数组的概念及模板

    (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...真可怕...

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

  • 相关阅读:
    修改Anaconda中Jupyter Notebook默认工作路径
    npm卸载和安装淘宝镜像原
    hash和history两种模式的区别
    威胁建模之WEB应用自动化截图
    Ubuntu系统清理
    Ubuntu环境的docker实践——ONOS
    BUUCTF-crypto:信息化时代的步伐
    BUUCTF-crypto:四面八方
    BUUCTF-crypto:[HDCTF2019]basic rsa
    BUUCTF-crypto:rsarsa
  • 原文地址:https://www.cnblogs.com/FloraLOVERyuuji/p/10382408.html
Copyright © 2020-2023  润新知