• BZOJ3238: [Ahoi2013]差异 (后缀自动机)


    Description

    Input

    一行,一个字符串S

    Output

    一行,一个整数,表示所求值

    Sample Input

    cacao

    Sample Output


    54

    HINT

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

    YY了后缀自动机的解法:

    首先题意就是让你求sigma(LCP(i,j)|i<j)

    将字符串反过来,考虑两个后缀对答案的贡献,其实就是节点x和y的lca节点包含的最长子串长度

    那么将SAM构出来,考虑当LCA为节点z时,有多少满足条件的(x,y),这个枚举z的相邻子节点,dp一下即可

    code:O(n) 2104ms

    #include<cstdio>
    #include<cctype>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    typedef long long LL;
    const int maxn=1000010;
    int n,to[maxn][26],fa[maxn],l[maxn],f[maxn],x[maxn],w[maxn],od[maxn],cnt=1,last=1;
    void extend(int c)
    {
        int p,q,np,nq;
        p=last;last=np=++cnt;l[np]=l[p]+1;f[np]=w[np]=1;
        for(;!to[p][c];p=fa[p]) to[p][c]=np;
        if(!p) fa[np]=1;
        else
        {
            q=to[p][c];
            if(l[p]+1==l[q]) fa[np]=q;
            else
            {
                nq=++cnt;l[nq]=l[p]+1;
                memcpy(to[nq],to[q],sizeof(to[q]));
                fa[nq]=fa[q];
                fa[q]=fa[np]=nq;
                for(;to[p][c]==q;p=fa[p]) to[p][c]=nq;
            }
        }
    }
    LL solve()
    {
        LL ans=0;
        for(int i=1;i<=cnt;i++) x[l[i]]++;
        for(int i=1;i<=n;i++) x[i]+=x[i-1];
        for(int i=1;i<=cnt;i++) od[x[l[i]]--]=i;
        for(int i=cnt;i;i--) f[fa[od[i]]]+=f[od[i]];
        for(int i=1;i<=cnt;i++)
        {
            ans+=(LL)w[fa[i]]*f[i]*l[fa[i]];
            w[fa[i]]+=f[i];
        }
        return ans;
    }
    char s[maxn];
    int main()
    {
        scanf("%s",s);
        n=strlen(s);
        for(int i=n-1;i>=0;i--) extend(s[i]-'a');
        LL ans=0;
        for(int i=1;i<=n;i++) ans+=(LL)i*(n-1);
        printf("%lld
    ",ans-2*solve());
        return 0;
    }
    View Code

    另外转一下hzwer的SA解法:

    --------------------------------------------------------------------------------------------------------------------------------------------蒟蒻与神犇的分界线--------------------------------------------

    显然后缀数组不是正确姿势。。。

    不过还是说说后缀数组的做法吧,bzoj总时限20s是能过的

    SA+rmq求lcp应该烂大街了,这题还不用rmq。。。

    首先求出h数组

    考虑h[i]在哪些区间内会成为最小值,这个用两次单调栈很容易就能解决

    还要处理一下由于h[i]可能相同造成的重复计数问题,具体看代码

    code O(nlogn) 13592ms

    #include<set>
    #include<map>
    #include<ctime>
    #include<queue>
    #include<cmath>
    #include<cstdio>
    #include<vector>
    #include<cstring>
    #include<cstdlib>
    #include<iostream>
    #include<algorithm>
    #define N 500005
    #define inf 1000000000
    #define pa pair<int,int>
    #define ll long long 
    using namespace std;
    ll ans;
    int n,k,p,q=1,top;
    int v[N],a[N],h[N],sa[2][N],rk[2][N];
    int st[N],l[N],r[N];
    char ch[N];
    void mul(int *sa,int *rk,int *SA,int *RK)
    {
        for(int i=1;i<=n;i++)v[rk[sa[i]]]=i;
        for(int i=n;i;i--)
            if(sa[i]>k)
                SA[v[rk[sa[i]-k]]--]=sa[i]-k;
        for(int i=n-k+1;i<=n;i++)SA[v[rk[i]]--]=i;
        for(int i=1;i<=n;i++)
            RK[SA[i]]=RK[SA[i-1]]+(rk[SA[i-1]]!=rk[SA[i]]||rk[SA[i-1]+k]!=rk[SA[i]+k]);
    }
    void presa()
    {
        for(int i=1;i<=n;i++)v[a[i]]++;
        for(int i=1;i<=30;i++)v[i]+=v[i-1];
        for(int i=1;i<=n;i++)sa[p][v[a[i]]--]=i;
        for(int i=1;i<=n;i++)
            rk[p][sa[p][i]]=rk[p][sa[p][i-1]]+(a[sa[p][i-1]]!=a[sa[p][i]]);
        for(k=1;k<n;k<<=1,swap(p,q))
            mul(sa[p],rk[p],sa[q],rk[q]);
        for(int k=0,i=1;i<=n;i++)
        {
            int j=sa[p][rk[p][i]-1];
            while(ch[j+k]==ch[i+k])k++;
            h[rk[p][i]]=k;if(k>0)k--;           
        }
    }
    void solve()
    {
        for(int i=1;i<=n;i++)ans+=(ll)i*(n-1);
        h[0]=-inf;
        for(int i=1;i<=n;i++)
        {
            while(h[i]<=h[st[top]])top--;
            if(st[top]==0)l[i]=1;
            else l[i]=st[top]+1;
            st[++top]=i;
        }
        h[n+1]=-inf;top=0;st[0]=n+1;
        for(int i=n;i;i--)
        {
            while(h[i]<h[st[top]])top--;
            if(st[top]==n+1)r[i]=n;
            else r[i]=st[top]-1;
            st[++top]=i;
        }
        for(int i=1;i<=n;i++)
            ans-=2LL*(i-l[i]+1)*(r[i]-i+1)*h[i];
    }
    int main()
    {
        scanf("%s",ch+1);
        n=strlen(ch+1);
        for(int i=1;i<=n;i++)a[i]=ch[i]-'a'+1;
        presa();
        solve();
        printf("%lld",ans);
        return 0;
    }
    View Code
  • 相关阅读:
    JAVA相关基础的知识吧
    Java测试调用.net 接口服务
    Java测试内存信息
    Java测试普通Java接口记录-TestHrmInterface
    那些年学不会的操作(写法/...)——记录一些靠搜索做过但总是记不住的东西
    正确的sybase批量插入语法
    新ZJJG项目相关接口开发记录-微信制证组成浅析
    bip项目的启用/调试+ 问题记录
    记录数组问题
    模糊匹配的查询条件/ 给下拉框加提示呢
  • 原文地址:https://www.cnblogs.com/wzj-is-a-juruo/p/4561157.html
Copyright © 2020-2023  润新知