• [BZOJ3238][AHOI2013]差异 [后缀数组+单调栈]


    题目地址 - GO->


    题目大意:

    给定一个长度为 n 的字符串S,令Ti表示它从第i个字符开始的后缀,求以下这个式子的值:

    1i<jnlen(Ti)+len(Tj)2×lcp(Ti,Tj)

    其中, len(a) 表示字符串 a 的长度, lcp(a,b) (longest common prefix)表示字符串 a 和字符串 b 的最长公共前缀。


    分析:

    我们将式子变一下形,可以容易的得到:

    [1i<jnlen(Ti)+len(Tj)] 2×[1i<jnlcp(Ti,Tj)]

    因为前半部分是求后缀长度和,而后缀长度是递减的,所以可以由等差数列公式得到1inlen(Ti)=n×(n+1)2,而在计算每一个len(Ti)时,它会被算n1次,所以前半部分我们就O(1)得到为

    1i<jnlen(Ti)+len(Tj)=(n1)×[1inlen(Ti)]=(n1)×(n×(n1)2)

    那么对于后面的,一看数据范围2n500000,总不能O(n2)的计算吧。

    所以这里运用了一个巧妙的方法,单调栈

    因为我们发现lcp是按照sa(后缀数组)中的rank单调的,而两个后缀之间的lcpheight数组的区间最小值,所以查询两个后缀的lcp可以用rmq预处理,但是对于多个如何处理呢?

    所以我们先对所有的下标按照rank排个序,然后往单调栈里面加,单调栈维护的lcp递增不减,每新加入一个lcp(Ti1,Ti)时,我们要计算Ti对于答案的贡献,那么由于栈中的lcp大小是单调递增不减的,所以当加入一个新的lcp时,要把栈顶大于这个新的值的元素给删除,并且还要消除比它大的这些元素的影响,而一个新的lcp的贡献等于之前lcp比它小的贡献加上lcp比它大的个数乘以它的lcp大小,要消除的影响就是每个比它大的lcp×大的个数。所以我们可以用一个单调栈来完成这个操作。

    代码:

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define ll long long
    using namespace std;
    const int M=1e6+10;
    int n,minv;
    int stk[M],top;
    char s[M];
    int cnt[M],rak[M],y[M],sa[M],A,p,len,h[M],all[M];
    ll ans,sumv,now;
    void getsa(){
        len=strlen(s)+1;n=len-1;A=256;
        for(int i=0;i<len;i++) ++cnt[rak[i]=s[i]];
        for(int i=1;i<=A;i++) cnt[i]+=cnt[i-1];
        for(int i=len-1;i>=0;i--) sa[--cnt[rak[i]]]=i;
        for(int k=1;k<=len;k<<=1){
            for(int i=0;i<=A;i++) cnt[i]=0;p=0;
            for(int i=len-k;i<len;i++) y[p++]=i;
            for(int i=0;i<len;i++) if(sa[i]>=k) y[p++]=sa[i]-k;
            for(int i=0;i<len;i++) ++cnt[rak[y[i]]];
            for(int i=1;i<=A;i++) cnt[i]+=cnt[i-1];
            for(int i=len-1;i>=0;i--) sa[--cnt[rak[y[i]]]]=y[i];
            swap(rak,y);p=1;rak[sa[0]]=0;
            for(int i=1;i<len;i++){
                if(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k]){
                    rak[sa[i]]=p-1;
                }else{
                    rak[sa[i]]=p++;
                }
            }
            if(p>=len) break;A=p;
        }
        for(int i=1;i<=n;i++) rak[sa[i]]=i;
    }
    void geth(){
        int k=0;
        for(int i=0;i<n;i++){
            if(!rak[i]) continue;
            if(k)--k;
            int j=sa[rak[i]-1];
            for(;s[i+k]==s[j+k];k++);
            h[rak[i]]=k;
        }
        for(int i=n;i>=1;i--) ++sa[i],rak[i]=rak[i-1];
    }
    int rmq[M][22];
    void initrmq(){
        for(int i=1;i<=n;i++) rmq[i][0]=h[i];
        for(int j=1;(1<<j)<=n;j++){
            for(int i=1;i+(1<<j)-1<=n;j++){
                rmq[i][j]=min(rmq[i][j-1],rmq[i+(1<<(j-1))][j-1]);
            }
        }
    }
    int lcp(int a,int b){
        if(a>n||b>n) return 0;
        if(a==b) return n-a+1;
        ++a;
        if(a>b)swap(a,b);
        int k=0;
        for(;(1<<(k+1))<=b-a+1;k++);
        return min(rmq[a][k],rmq[b-(1<<k)+1][k]);
    }
    int ls[M];
    int main(){
        scanf("%s",s);
        getsa();
        geth();
        initrmq();
        for(int i=1;i<=n;i++) ls[i]=rak[i];
        sort(ls+1,ls+n+1);//按照rank排序
        for(int i=2;i<=n;i++){
            minv=lcp(ls[i-1],ls[i]);//rank相邻的两个lcp是比较大的,因为相似度比较高。
            now=0;
            while(top&&stk[top]>=minv){
                now+=all[top];//累计大的个数
                ans-=(1ll*all[top]*stk[top]);//减去大的影响
                --top;
            }
            stk[++top]=minv;all[top]=now+1;//当前的lcp=minv,个数=前面大的个数+自己。
            ans+=(1ll*stk[top]*all[top]);//加上当前的贡献
            sumv+=ans;//每次统计到答案里面
        }
        printf("%lld
    ",(1ll*n*(n+1))/2ll*(n-1)-2ll*sumv);
        return 0;
    }

    这个题,看其他人还有后缀树+虚树,后缀自动机等做法,下面有个相似的题目,也可以用这个方法:BZOJ3879 SVT

    若有错,请大佬指出,Orz。

  • 相关阅读:
    3、MFC框架程序剖析
    SharePoint 2010 Form 认证 之 IIS 添加数据
    SharePoint 2010 Form 认证 之 配制
    在线压缩
    关于checkbox的各种情况
    C#注册时的邮箱验证
    C#中用到的加密和解密函数
    汉字验证码
    HFS共享服务器在使用路由器的局域网中的共享到外网的方法
    LDAP 服务
  • 原文地址:https://www.cnblogs.com/VictoryCzt/p/10053434.html
Copyright © 2020-2023  润新知