[BZOJ 3238][AHOI 2013]差异(后缀数组+单调栈)

142 篇文章 0 订阅
98 篇文章 0 订阅

题目链接

http://www.lydsy.com/JudgeOnline/problem.php?id=3238

思路

感谢http://www.cnblogs.com/Tunix/p/4211675.html非常详细的讲解。

首先很容易预处理出所有的 T(i)+T(j) 总和,为 n(n+1)(n1)2 (随便找个数列玩玩可以发现每个数字各算了 n1 次,共有 n 个数字,它们之和为n(n+1)2)

然后就是要减去所有的LCP值了。暴力做法就是枚举 ij ,暴力扫或者RMQ求出后缀 ij 的LCP值。但是这道题这样做显然不可取。可以考虑枚举LCP值,看有多少对后缀 (i,j) 的LCP是这个值。每个 height[i] 均有可能是某些后缀对的LCP值,这些后缀对也显然一定在某个区间 [L[i],R[i]] 中,且对于任意的 L[i]<=j<=R[i] , height[j]>=height[i] 。我们可以通过两次单调栈在 O(n) 时间内分别求出 L[]R[] 数组,而LCP值= height[i] 的后缀对个数就是 2(iL[i]+1)(R[i]i+1) (因为这里的两个后缀对可以都是 i ,所以要加1,而且后缀对都是无序的,所以要乘2)。

但是这样做可能会重复计算。即对于不同的i,j,可能出现 [L[i],R[i]][L[j],R[j]] 两段区间重合的情况,避免这种情况出现的解决办法就是让 L[i] 遇到相同的 height 后可以往左走,而 R[i] 就不行

代码

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>

#define MAXN 1000000

using namespace std;

typedef long long int LL;

int sa[MAXN],rank[MAXN],height[MAXN];
int wa[MAXN],wb[MAXN],wv[MAXN],cnt[MAXN];

bool cmp(int *r,int a,int b,int c) //(a,a+c)与(b,b+c)比较
{
    return (r[a]==r[b])&&(r[a+c]==r[b+c]);
}

void SA(int *r,int n,int m) //n比实际的r长度大1
{
    int i,j,p;
    int *x=wa,*y=wb;
    for(i=0;i<m;i++) cnt[i]=0;
    for(i=0;i<n;i++) cnt[(x[i]=r[i])]++;
    for(i=1;i<m;i++) cnt[i]+=cnt[i-1];
    for(i=n-1;i>=0;i--) sa[--cnt[x[i]]]=i;
    for(j=1,p=1;p<n;j*=2,m=p)
    {
        for(p=0,i=n-j;i<n;i++) y[p++]=i;
        for(i=0;i<n;i++) if(sa[i]>=j) y[p++]=sa[i]-j; //y[i]=当前按照第二关键字后缀i的排名
        for(i=0;i<n;i++) wv[i]=x[y[i]]; //wv=按照第一关键字排序好的字符串
        for(i=0;i<m;i++) cnt[i]=0;
        for(i=0;i<n;i++) cnt[wv[i]]++;
        for(i=1;i<m;i++) cnt[i]+=cnt[i-1];
        for(i=n-1;i>=0;i--) sa[--cnt[wv[i]]]=y[i];
        swap(x,y);
        for(p=1,x[sa[0]]=0,i=1;i<n;i++)
            x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
    }
}

void calc(int *r,int n)
{
    int i,j,k=0;
    for(i=1;i<=n;i++) rank[sa[i]]=i;
    for(i=0;i<n;height[rank[i++]]=k)
        for(k?k--:0,j=sa[rank[i]-1];r[i+k]==r[j+k];k++);
}

int n,m,num[MAXN],stack[MAXN]; //stack是单调栈
char s[MAXN];
int L[MAXN],R[MAXN]; //height[i]对应的区间是[L[i],R[i]]

int main()
{
    scanf("%s",s);
    n=strlen(s);
    for(int i=0;i<n;i++) num[i]=s[i];
    LL ans=(LL)((LL)n*(n-1)*(n+1))/2;
    SA(num,n+1,255);
    calc(num,n);
    height[1]=height[n+1]=0; //!!!!!!
    int top=0;
    stack[++top]=1;
    for(int i=1;i<=n;i++) //求每个height[i]对应的左区间
    {
        while(top&&height[stack[top]]>height[i]) top--;
        if(top) L[i]=stack[top]+1;
        else L[i]=1;
        stack[++top]=i;
    }
    top=0;
    stack[++top]=n; //!!!!!
    for(int i=n;i>=1;i--) //求每个height[i]对应的右区间
    {
        while(top&&height[stack[top]]>=height[i]) top--;
        if(top) R[i]=stack[top]-1;
        else R[i]=n;
        stack[++top]=i;
    }
    for(int i=2;i<=n;i++)
    {
        ans-=(LL)2*(LL)height[i]*(LL)(i-L[i]+1)*(LL)(R[i]-i+1);
    }
    printf("%lld\n",ans);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值