BZOJ 3238 [Ahoi2013]差异 后缀自动机

博客介绍了如何利用后缀自动机解决BZOJ 3238题目的差异问题,通过反向构建后缀自动机,计算节点间的LCA来得到最长公共子串,并采用动态规划进行枚举和求解。
摘要由CSDN通过智能技术生成

题目大意:
这里写图片描述

len(Ti)+len(Tj)枚举一下就好,主要是如何算sigma{lcp(Ti,Tj)}

将原串反过来建后缀自动机,两个节点的lca即最长子串就是这两个节点代表的串的lcp

于是枚举、DP一下即可

#include <cstdio>
#include <algorithm>
#include <cstring>
#define N 500005
using namespace std;
typedef long long LL;
struct Node {
    Node *ch[26],*pa;
    int val,cnt,siz;
    bool vis;
    Node() {}
    Node(int _val):val(_val),cnt(0),siz(0),pa(NULL),vis(false) {
        memset(ch,0,sizeof ch);
    }
    void* operator new(size_t) {
        static Node *C,*mempool;
        if(C==mempool) mempool=(C=new Node[1<<20])+(1<<20);
        return C++;
    }
}*root=new Node(0),*last;
void extend(char c) {
    int z=c-'a';
    Node *p=last,*np=new Node(p->val+1);
    last=np;
    while(p && !p->ch[z]) p->ch[z]=np, p=p->pa;
    if(!p) {
        np->pa=root;
        return ;
    }
    Node* q=p->ch[z];
    if(q->val==p->val+1) {
        np->pa=q;
        return ;
    }
    Node* nq=new Node(p->val+1);
    nq->pa=q->pa;
    q->pa=np->pa=nq;
    memcpy(nq->ch,q->ch,sizeof nq->ch);
    while(p && p->ch[z]==q) p->ch[z]=nq, p=p->pa;
    return ;
}
void init(char s[]) {
    last=root;
    int len=strlen(s);
    Node* o=root;
    for(int i=0;i<len;i++)
        extend(s[i]), o=o->ch[s[i]-'a'], o->siz++, o->cnt++;
    return ;
}
LL solve() {
    static Node *q[N*2],*s[N*2];
    static int t[N];
    LL tmp=0;
    int l=0,r=0;
    q[r++]=root;
    while(l<r) {
        Node* o=q[l++];
        t[o->val]++;
        for(int i=0;i<26;i++)
            if(o->ch[i] && !o->ch[i]->vis)
                q[r++]=o->ch[i], o->ch[i]->vis=true;
    }
    for(int i=1;i<=r;i++) t[i]+=t[i-1];
    for(int i=r-1;~i;i--) s[t[q[i]->val]--]=q[i];
    for(int i=r;i;i--)
        if(s[i]->pa)
            s[i]->pa->siz+=s[i]->siz;
    for(int i=1;i<r;i++) {
        tmp+=(LL)q[i]->pa->cnt*q[i]->siz*q[i]->pa->val;
        q[i]->pa->cnt+=q[i]->siz;
    }
    return tmp;
}
int main() {
    static char s[N],str[N];
    scanf("%s",str);
    int len=strlen(str);
    for(int i=0;i<len;i++) s[i]=str[len-i-1];
    init(s);
    LL ans=0;
    for(int i=0;i<len;i++) ans+=(LL)(i+1)*(len-1);
    printf("%lld\n",ans-solve()*2);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值