• 后缀自动机 牛客[AHOI2013]差异


    题目链接:https://ac.nowcoder.com/acm/problem/19894

    后缀自动机推荐博客:https://www.luogu.org/blog/Kesdiael3/hou-zhui-zi-dong-ji-yang-xie

      这里我们必须要先求出给出字符串中所有后缀两两之间的最长公共前缀长度之和,想用后缀自动机做,在后缀自动机上的每一个点都代表了原串中的多个唯一子串,但是这里面最长的那个子串一定是原串的一个前缀,并且这个点所代表的其他子串一定是这个前缀的后缀。所以我们可以暂且理解为SAM上的每一个点都代表了原串的一个前缀,这个点代表的其他子串这里先不管。

      然后SAM上每一个点都会有一个fa指针,fa指针指向的那个节点所代表的最长子串也是原串的一个前缀,并且这个前缀同时还是当前点所代表的前缀的后缀。我们以自动机上的每一个叶子节点为起点向它的fa方向遍历,在遍历的过程中我们就可以算出所有前缀两两之间的最长公共后缀之和了。然后我们发现我们求出的东西刚好和题目需要我们求出的东西相反,于是我们把题目给出的字符串翻转一下,用这个翻转之后的字符串来构到自动机,然后题目就变成了求翻转字符串的所有前缀两两之间的最长后缀之和,然后用总数减去这个和就行了。

    代码:

    #include<iostream>
    #include<cstring>
    #include<algorithm>
    #include<queue>
    #include<map>
    #include<stack>
    #include<cmath>
    #include<vector>
    #include<set>
    #include<cstdio>
    #include<string>
    #include<deque> 
    using namespace std;
    typedef long long ll;
    #define eps 1e-8
    #define INF 0x3f3f3f3f
    #define maxn 2000005
    struct node{
        int len,fa;
        int ch[26];
    }trie[maxn];
    int last,tot;
    ll len;
    ll sum,ans;
    char str[maxn];
    ll size[maxn];
    int c[maxn],x[maxn];
    void init(){
        last=tot=1;
        memset(size,0,sizeof(size));
        sum=ans=0;
    } 
    void insert(int c){
        int p=last;
        int np=last=++tot;
        size[tot]=1;
        trie[np].len=trie[p].len+1;
        for(;p&&trie[p].ch[c]==0;p=trie[p].fa) trie[p].ch[c]=np;
        if(p==0) trie[np].fa=1;
        else{
            int q=trie[p].ch[c];
            if(trie[p].len+1==trie[q].len) trie[np].fa=q;
            else{
                int nq=++tot;
                trie[nq]=trie[q];
                trie[nq].len=trie[p].len+1;
                trie[q].fa=trie[np].fa=nq;
                for(;p&&trie[p].ch[c]==q;p=trie[p].fa)
                trie[p].ch[c]=nq;
            }
        }
    } 
    void cal(){
        //基数排序,按照len值从小到大排序 
        for(int i=0;i<=tot;i++) c[i]=0;
        for(int i=2;i<=tot;i++) c[trie[i].len]++;
        for(int i=2;i<=tot;i++) c[i]+=c[i-1];
        for(int i=tot;i>=2;i--) x[c[trie[i].len]--]=i;
        //从叶子节点开始遍历fail树 
        for(int i=tot;i>=2;i--){
            int c=x[i];
            //(当前节点代表的字符串出现的次数)*(它fail指向的字符串(是前一个字符串的后缀)的长度)*乘以2
            //这里之所以要再乘以size[trie[i].fa]是因为fail指向的那个节点的size可能为0,它可能是我们在构造sam时创建用来将一个点划分为两个点的节点 
            sum+=2*size[c]*(trie[trie[c].fa].len)*size[trie[c].fa];
            //因为trie[c].fa代表的前缀是trie[c]代表前缀的后缀,所以我们要把trie[c]出现的次数叠加到trie[c].fa上面 
            size[trie[c].fa]+=size[c];
        }
    }
    int main()
    {
        scanf("%s",str);
        init();
        //我们需要求的是str所有后缀两两之间的最长公共前缀长度之和,所以我们将str翻转
        //这样问题就转化成为了求翻转之后字符串的所有前缀两两之间的最长公共后缀之和了 
        len=strlen(str);
        for(int i=len-1;i>=0;i--){ 
            insert(str[i]-'a');
        }
        cal();
        ans=len*(len+1)/2*(len-1);
        printf("%lld
    ",ans-sum);
        return 0;
    }
  • 相关阅读:
    apple ID的重要性
    使用spring实现邮件的发送(含测试,源码,注释)
    使用java底层实现邮件的发送(含测试,源码)
    使用java底层实现邮件的发送(含测试,源码)
    java实现邮件发送准备工作(前期配置)
    java实现邮件发送准备工作(前期配置)
    Java实现最电话号码的简单加密源码
    Java实现最电话号码的简单加密源码
    java实现加密电话号码,有具体的加密流程注释
    java实现加密电话号码,有具体的加密流程注释
  • 原文地址:https://www.cnblogs.com/6262369sss/p/11740662.html
Copyright © 2020-2023  润新知