bzoj 3238 差异

给出一个长n的字符集为小写字母的字符串,求 1i<jnlen(Suffixi)+len(Suffixj)2×len(lcp(Suffixi,Suffixj))

Suffixx 代表从 x 开始的后缀,lcp(stra,strb)表示 stra , strb 的最长公共前缀, len(str) 表示 str 的长度


如果我们拥有这个字符串的后缀树,将每个后缀所对应的节点记为标记点。

对每一个标记点对,我们统计的其实是这两个点到根的距离和减去根到他们 LCA 的距离的二倍。也就是这两个点之间的路径的距离。

那我们要求的实际上就是树上所有标记点对的距离和。


以下不加证明的给出一个结论

一个字符串翻转的后缀自动机的 parent 树就是在这个字符串的后缀树上将后缀对应的节点作为关键点所建立的虚树, parent 树中子节点和父节点的 len 的差值就是这两个点在后缀树上的距离。


有了上面那个结论之后我们就可以用 SAM 建立后缀树,在后缀树上做一下树dp就可以了。

#include<bits/stdc++.h>
using namespace std;

const int maxn = 512345,mlen = 26;
#define LL long long 

LL ans;
LL dp[maxn*2],siz[maxn*2];
vector<pair<int,int> >edge[maxn*2];
void Link(int st,int ed,int v){
    edge[st].push_back(make_pair(ed,v));
    edge[ed].push_back(make_pair(st,v));
}

struct Sam{
    int len[maxn*2],fa[maxn*2],nex[maxn*2][mlen];
    int val[maxn*2];
    int _cnt,root,omg;
    int newNode(int L = 0){
        len[_cnt] = L;
        memset(nex[_cnt],fa[_cnt] = -1,sizeof(nex[_cnt]));
        return _cnt++;
    }
    void init(){
        _cnt = 0;
        memset(val,0,sizeof(val));
        root = omg = newNode();
    }
    void extend(int x){
        int ox = newNode(len[omg]+1);
        val[ox]++;
        while(omg != -1 && nex[omg][x] == -1){
            nex[omg][x] = ox;
            omg = fa[omg];
        }
        if(omg == -1) fa[ox] = root;
        else{
            int omgx = nex[omg][x];
            if(len[omgx] == len[omg]+1) fa[ox] = omgx;
            else{
                int mgx = newNode(len[omg]+1);
                for(int i=0;i<mlen;i++)
                    nex[mgx][i] = nex[omgx][i];
                fa[mgx] = fa[omgx];
                fa[omgx] = fa[ox] = mgx;
                while(omg != -1 && nex[omg][x] == omgx)
                    nex[omg][x] = mgx,omg = fa[omg];
            }
        }
        omg = ox;
    }
    void build(char *arr){
        init();
        for(int i=0;arr[i];i++){
            extend(arr[i] - 'a');
        }
    }
    void treeBuild(){
        for(int i=0;i<_cnt;i++){
            edge[i].clear();
            dp[i] = 0 , siz[i] = val[i];
        }
        for(int i=0;i<_cnt;i++){
            if(fa[i] != -1){
                Link(i,fa[i],len[i] - len[fa[i]]);
            }
        }
    }
}SAM;

char arr[maxn];

void dfs(int st,int fa = -1){
    for(vector<pair<int,int> >::iterator it = edge[st].begin();
        it != edge[st].end();it++){
        pair<int,int> x = *it;
        if(x.first == fa) continue;
        dfs(x.first,st);
        ans += dp[st] * siz[x.first] + (dp[x.first] + siz[x.first] * x.second) * siz[st];
        dp[st] += dp[x.first] + siz[x.first] * x.second; 
        siz[st] += siz[x.first];
    }
}

int main(){
    scanf("%s",arr);
    int len = strlen(arr);
    reverse(arr,arr+len);
    SAM.build(arr);
    SAM.treeBuild();
    ans = 0;
    dfs(SAM.root);
    printf("%lld\n",ans);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值