后缀数组从入门到省选

1,后缀 SA 的讲解
2,LCP 的讲解

一,SA 后缀数组(倍增)

后缀数组 sa[i]:表示所有后缀在排完序后,排名为i的后缀在原串中的位置。
名次数组rk[i]:表示所有后缀在排序完后,原字符串中第 i 名现在的排名。

在这里插入图片描述

  • 基于倍增排序之后,直接转化为双关键词基数排序
  • 1,重复使用
  • 2,空串保送
  • 3,从后遍历保证相对顺序

二,LCP 最长公共前缀

h e i g h t [ i ] height[i] height[i]:表示 s u f f [ s a [ i ] ] suff[sa[i]] suff[sa[i]] s u f f [ s a [ i − 1 ] ] suff[sa[i−1]] suff[sa[i1]]的最大公共前缀,也就是排名完后两个相邻的后缀的最长公共前缀。
h [ i ] h[i] h[i]:等于 h e i g h t [ r a n k [ i ] height[rank[i] height[rank[i] s u f f [ i ] suff[i] suff[i]和排序后在它前一名的后缀的最长公共前缀。

定义 L C P ( i , j ) = l c p ( s u f ( s a [ i ] ) , s u f ( s a [ j ] ) LCP(i,j)=lcp(suf(sa[i]),suf(sa[j]) LCP(i,j)=lcp(suf(sa[i]),suf(sa[j])
在这里插入图片描述

1,LCP lemma(传递性)

1,对任意 1 ≤ i < j < k ≤ n , L C P ( i , k ) = m i n { L C P (   i   ,   j   ) , L C P (   j   ,   k   ) } 1≤i<j<k≤n,LCP(i,k)=min\{LCP(~i~,~j~),LCP(~j~,~k~)\} 1i<j<knLCP(i,k)=min{LCP( i , j ),LCP( j , k )}
2,设 i + 1 ≤ k ≤ j , L C P ( i , j ) = m i n { L C P (   k − 1   ,   k ) } i+1 \le k \le j,LCP(i,j)=min\{LCP(~k−1~,~k)\} i+1kjLCP(i,j)=min{LCP( k1 , k)}

  • 强调:这里的 i i i j j j k k k 均指代排名
    证明:
    正向由定义易知: L C P ( i , k ) > = m i n { L C P ( i , j ) , L C P ( j , k ) } LCP(i,k)>=min\{LCP(i,j),LCP(j,k)\} LCP(i,k)>=min{LCP(i,j),LCP(j,k)}
    反向夹逼定理如图

2,LCP Theorem (位序相关性)

定义: h ( i ) = h e i g h t ( r k [ i ] ) h(i)=height(rk[i]) h(i)=height(rk[i])
性质: h ( i ) ≥ h ( i − 1 ) − 1 h(i) \ge h(i-1)−1 h(i)h(i1)1
证明如图
在这里插入图片描述

求法

void SA()
{
    for (int i = 1; i <= n; i ++ ) c[x[i]=s[i]]++;
    for (int i = 2; i <= m; i ++ ) c[i]+=c[i-1];
    for (int i = n; i >= 1; i -- ) sa[c[x[i]]--]=i;
    for(int k = 1;k <= n; k<<=1)
    {
        int num = 0;
        for(int i = n-k+1; i <= n; i ++ ) y[++num]=i;
        for (int i = 1; i <= n; i ++ )
        {
            if(sa[i]<=k)continue;
            y[++num]=sa[i]-k;
        }
        for (int i = 1; i <= m; i ++ )c[i]=0;
        for (int i = 1; i <= n; i ++ )c[x[i]]++;
        for (int i = 2; i <= m; i ++ )c[i]+=c[i-1];
        for (int i = n; i >= 1; i -- ) sa[c[x[y[i]]]--]=y[i],y[i]=0;
        
        swap(x,y);
        x[sa[1]]=1;
        num = 1;
        for (int i = 2; i <= n; i ++ )
        {
            if(y[sa[i]]==y[sa[i-1]] && y[sa[i]+k]==y[sa[i-1]+k]) 
            {
                x[sa[i]] = num;
            }
            else x[sa[i]] = ++num;
        }
        
        if(num==n)break;
        else m = num;
    }
}

void get_height()
{
    for (int i = 1; i <= n; i ++ ) rk[sa[i]] = i;
    for (int i = 1, k = 0; i <= n; i ++)
    {
        if(rk[i]==1)continue;
        if(k) k--;
        int j = sa[rk[i]-1];
        while (i + k <= n && j + k <= n && s[i+k] == s[j+k] )k++;
        height[rk[i]] = k;
    }
}

三,习题

1,P4248 【[AHOI2013]差异】

粉兔的讲解

求解很多个左端点和右端点都在变的LCP

  • 转化为贡献型,对于一个height[i],思考他的贡献对象
  • 注意到 lcp lemma,就是区间最小值,所以记录单调递减栈的每个点的出栈位置和入栈位置,分成两段,贡献做积*2
  • 开ll,推式子

在这里插入图片描述

int main()
{
    scanf("%s",str+1);
    len=strlen(str+1);
    num=128;
    SA();
    get_height();

    int top;
    st[top=1]=1;
    
    rep(i,2,len)
    {
        while(top && height[st[top]]>height[i]) r[st[top--]]=i;
        l[i]=st[top];
        st[++top]=i;
    }
    
    while(top)r[st[top--]]=len+1;

    ll ans= (ll)(len-1)*(len+1)*len/2;
    for (int i=2;i<=len;i++)ans -=2ll*(r[i]-i)*(i-l[i])*height[i];

    printf("%lld\n", ans);

}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

流苏贺风

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值