bzoj3238 [Ahoi2013]差异

http://www.elijahqi.win/2018/02/19/bzoj3238/
Description
这里写图片描述
Input

一行,一个字符串S
Output

一行,一个整数,表示所求值
Sample Input
cacao
Sample Output

54

HINT

2<=N<=500000,S由小写英文字母组成

学农之前想做的题来着的当时怎么也想不明白现在就好一些了

首先看这个式子的前一部分 由于每个字符串恰好出现了n-1次 所以 答案是

(n-1)∑len[i]=(n-1)*n(n+1)/2。 那么显然可以o1求出 那么后面的怎么办 一个显然的方法是暴力枚举o(n^2) 时间复杂度显然难以接受 怎么办 考虑 每个后缀对答案的贡献 每个后缀对答案的贡献一定是这个后缀的height能当最小值的一段区间 大概是前面的个数*后面的个数 那 怎么搞 由于这个顺序并不影响我 我可以单独只考虑height 那么想到 o(n)时间,单调栈求某个点向左能扩展到哪,向右能扩展到哪。两边扩展长度的乘积不就是以该点的值为最小值的区间个数 然后这个最小值乘区间个数就是对答案的贡献 然后最后整体减去这个贡献即可 注意存在height相同的情况 这时候就需要我们更改下判断条件 使得重复的只被计算一次贡献

#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
#define N 550000
using namespace std;
int top,q[N],n,m,rk1[N<<1],rk[N<<1],height[N],sa[N],cnt[N],tmp[N],k,l[N],r[N];
char s[N];ll ans;
int main(){
    freopen("bzoj3238.in","r",stdin);
    scanf("%s",s+1);n=strlen(s+1);
    for (int i=1;i<=n;++i) cnt[s[i]]=1;
    for (int i=1;i<=200;++i) cnt[i]+=cnt[i-1];
    for (int i=1;i<=n;++i) rk[i]=cnt[s[i]];m=26;
    for (int p=1;k!=n;p<<=1,m=k){
        for (int i=1;i<=m;++i) cnt[i]=0;
        for (int i=1;i<=n;++i) ++cnt[rk[i+p]];
        for (int i=1;i<=m;++i) cnt[i]+=cnt[i-1];
        for (int i=n;i;--i) tmp[cnt[rk[i+p]]--]=i;
        for (int i=1;i<=m;++i) cnt[i]=0;
        for (int i=1;i<=n;++i) ++cnt[rk[i]];
        for (int i=1;i<=m;++i) cnt[i]+=cnt[i-1];
        for (int i=n;i;--i) sa[cnt[rk[tmp[i]]]--]=tmp[i];
        memcpy(rk1,rk,sizeof(rk)>>1);rk[sa[1]]=k=1;
        for (int i=2;i<=n;++i){
            if (rk1[sa[i]]!=rk1[sa[i-1]]||rk1[sa[i]+p]!=rk1[sa[i-1]+p]) ++k;
            rk[sa[i]]=k;
        }
    }k=0;ans+=(ll)(n-1)*n*(n+1)>>1;
    for (int i=1;i<=n;++i){
        if (rk[i]==1) continue;
        k=k==0?0:k-1;
        while (s[i+k]==s[sa[rk[i]-1]+k]) ++k;
        height[rk[i]]=k;
    }
    for (int i=2;i<=n;++i){
        while (top&&height[i]<=height[q[top]]) r[q[top--]]=i-1;
        q[++top]=i;
    }
    while(top) r[q[top--]]=n;
    for (int i=n;i>=2;--i){
        while (top&&height[i]<height[q[top]]) l[q[top--]]=i+1;
        q[++top]=i;
    }while(top) l[q[top--]]=2;
    for (int i=2;i<=n;++i) ans-=2LL*(i-l[i]+1)*(r[i]-i+1)*height[i];
    printf("%lld",ans);
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值