后缀数组+单调栈 【Ahoi2013】bzoj3238 差异

题目大意:
这里写图片描述

题目分析:
这个公式的前两项可以提出来单算,这两项的总和应该是(n+1)n/2 (n+2)。
问题就转化成了求任意两个后缀的lcp之和。
我们知道两个后缀的lcp就是height数组取min,那么反过来想,一个位置能影响到的就是自己前面和后面且height值大于当前位置height值的连续的部分(按照个人理解就是以这个位置的lcp当作桥梁,算出一部分lcp的长度), 问题转化为了求左右第一个比自己小的数的位置,单调栈就可以解决这个问题。

注意事项:
1、结果很大会爆int,要用long long来存储和计算最终答案,但是不能全都用long long类型,因为long long运算很慢,会TLE;
2、单调栈第一个弹栈的判断条件打”>=”,第二个要打”>”,否则会重复计算出错。

代码如下:

#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<string>
#include<cstring>
#include<iostream>
#define N 1200000
using namespace std;
int tot;
char s[N];
int sa[N],rnk[N],X[N],Y[N],tmp[N],sum[N],t,hi[N];
long long sta[N],l[N],r[N],top;
long long ans;
void get_rank()
{
    t=0;
    for(int i=0;i<=127;i++) sum[i]=0;
    for(int i=1;i<=tot;i++) sum[s[i]]++;
    for(int i=1;i<=127;i++) sum[i]+=sum[i-1];
    for(int i=tot;i>=1;i--) tmp[sum[s[i]]--]=i;
    for(int i=1;i<=tot;i++)
    {
        if(i==1 || s[tmp[i]]!=s[tmp[i-1]]) t++;
        rnk[tmp[i]]=t;
    }
}
void radix_sort(int key[],int order[])
{
    for(int i=0;i<=tot;i++) sum[i]=0;
    for(int i=1;i<=tot;i++) sum[key[i]]++;
    for(int i=1;i<=tot;i++) sum[i]+=sum[i-1];
    for(int i=tot;i>=1;i--) tmp[sum[key[order[i]]]--]=order[i];
    for(int i=1;i<=tot;i++) order[i]=tmp[i];
}
void get_height()
{
    for(int i=1;i<=tot;i++)
    {
        if(rnk[i]==1) continue;
        int j=max(hi[rnk[i-1]]-1,0),k=sa[rnk[i]-1];
        while(s[k+j]==s[i+j]) j++;
        hi[rnk[i]]=j;
    }
}
void prefix_array()
{
    get_rank();
    for(int j=1;j<=tot;j<<=1)
    {
        for(int i=1;i<=tot;i++)
        {
            X[i]=rnk[i];
            Y[i]=i+j>tot?0:rnk[i+j];
            sa[i]=i;
        }
        radix_sort(Y,sa);
        radix_sort(X,sa);
        t=0;
        for(int i=1;i<=tot;i++)
        {
            if(i==1 || X[sa[i]]!=X[sa[i-1]] || Y[sa[i]]!=Y[sa[i-1]]) t++;
            rnk[sa[i]]=t;
        }
    }
    get_height();
}
int main()
{
    scanf("%s",s+1);
    tot=strlen(s+1);
    prefix_array();
    top=0;
    for(int i=1;i<=tot;i++)
    {
        while(hi[sta[top]]>=hi[i] && top>0) top--;
        if(top==0) l[i]=1;
        else l[i]=sta[top]+1;
        sta[++top]=i;
    }
    top=0;
    for(int i=tot;i>=1;i--)
    {
        while(hi[sta[top]]>hi[i] && top>0) top--;
        if(top==0) r[i]=tot;
        else r[i]=sta[top]-1;
        sta[++top]=i;
    }
    long long o=tot;
    ans=((o+1)*o*(o-1))/2;
    for(long long i=1;i<=tot;i++) ans-=hi[i]*(r[i]-i+1)*(i-l[i]+1)*2ll;
    printf("%lld\n",ans);
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值