P4248 [AHOI2013]差异 (后缀数组height[]+单调栈)

题意:

给定一个长度为n的字符串 S,令Ti表示它从第i个字符开始的后缀,求:
在这里插入图片描述

解法:

显然前面的T(i)和T(j)是可以提出来直接算的,
考虑每个后缀作为T(i)和T(j)的次数:
1.作为T(i),那么对应n-i个T(j),因此次数为n-i
2.作为T(j),那么对应i-1个T(i),因此次数为i-1
综上得:每个后缀出现的总次数为n-i+i-1=n-1次

因此前面一段T(i)和T(j)部分的贡献为1(n-1)+2(n-1)+3(n-3)…n(n-1),(长度为1的后缀到长度为n的后缀)
即n(n+1)/2(n-1)。

然后考虑如何计算式子后面的lcp部分:
后缀数组中height[]的重要结论:lcp(i,j)=min{height[i+1],height[i+2]…height[j] }
那么问题可以变为,计算每个height是多少对(i,j)的lcp,即计算每个height的贡献,
因为lcp是对height取min,所以就是计算每个height[i]能作为多少个区间的最小值,
这是一个经典的单调栈问题,计算出height[i]作为最小值向左和向右的最大扩展位置L[i]和R[i],
那么区间数量就是(i-L[i]+1)*(R[i]-i+1),

设区间数量为cnt,那么对答案的贡献就是-2* height[i]*cnt

code:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxm=4e6+5;
struct SA{
    static const int N=4e6+5;
    char s[N];
    int sa[N],rk[N],oldrk[N<<1],id[N],px[N],cnt[N];
    int ht[N];
    int n;//字符串长度
    int m=300;//初始字符集大小
    bool cmp(int x,int y,int w){
        return oldrk[x]==oldrk[y]&&oldrk[x+w]==oldrk[y+w];
    }
    void getSA(){
        int i,p,w;
        for(i=1;i<=n;i++)cnt[rk[i]=s[i]]++;
        for(i=1;i<=m;i++)cnt[i]+=cnt[i-1];
        for(i=n;i>=1;i--)sa[cnt[rk[i]]--]=i;
        //
        for(w=1;w<n;w<<=1,m=p){
            for(p=0,i=n;i>n-w;i--)id[++p]=i;
            for(i=1;i<=n;i++)if(sa[i]>w)id[++p]=sa[i]-w;
            memset(cnt,0,sizeof cnt);
            for(i=1;i<=n;i++)cnt[px[i]=rk[id[i]]]++;
            for(i=1;i<=m;i++)cnt[i]+=cnt[i-1];
            for(i=n;i>=1;i--)sa[cnt[px[i]]--]=id[i];
            memcpy(oldrk,rk,sizeof rk);
            for(p=0,i=1;i<=n;i++){
                rk[sa[i]]=cmp(sa[i],sa[i-1],w)?p:++p;
            }
        }
    }
    void getHT(){
        for(int i=1,k=0;i<=n;i++){
            if(k)k--;
            while(s[i+k]==s[sa[rk[i]-1]+k])k++;
            ht[rk[i]]=k;
        }
    }
}sa;
int l[maxm],r[maxm];
signed main(){
    //input
    scanf("%s",sa.s+1);
    sa.n=strlen(sa.s+1);
    //
    sa.getSA();
    sa.getHT();
    //单调栈部分
    sa.ht[0]=sa.ht[sa.n+1]=-1;  //保证全部元素出栈
    stack<int>stk;
    for(int i=1;i<=sa.n+1;i++){
        while(!stk.empty()&&sa.ht[stk.top()]>=sa.ht[i]){
            r[stk.top()]=i-1;
            stk.pop();
        }
        stk.push(i);
    }
    while(!stk.empty())stk.pop();
    for(int i=sa.n;i>=0;i--) {
        while(!stk.empty()&&sa.ht[stk.top()]>sa.ht[i]){
            l[stk.top()]=i+1;
            stk.pop();
        }
        stk.push(i);
    }
    //计算答案部分
    ll ans=1ll*sa.n*(sa.n+1)/2*(sa.n-1);
    for(int i=1;i<=sa.n;i++){
        ll temp=1ll*sa.ht[i]*(i-l[i]+1)*(r[i]-i+1);
        ans-=2*temp;
    }
    cout<<ans<<endl;
    return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值