@bzoj - 3238@ [Ahoi2013]差异

目录


@description@

给定一个长度为 n 的字符串 S,令 Ti 表示它从第 i 个字符开始的后缀。求:
\[\sum_{1\le i < j \le n}((len(Ti) -lcp(Ti, Tj)+(len(Tj)-lcp(Ti, Tj))\]
其中 lcp 是最长公共前缀。

input
一个长度为 n 的字符串S。2 <= n <= 500000,S由小写英文字母组成。
output
一个整数,表示所求值。

sample input
cacao
sample output
54

@solution@

那个式子我们可以分两部分求解:len 和 lcp。

len 部分:每一个后缀的 len 都会统计 n-1 次。所以它对答案的贡献为 \((1+2+...+n)*(n-1)\)。把等差数列的求和公式代进去:\(\frac{(n-1)*n*(n+1)}{2}\)

lcp 部分:我们把原串翻转,则原串中的后缀对应新串中的前缀,我们要求解原串中的最长公共前缀就是新串中的最长公共后缀。
一个结点的 fa 所表示的结点一定是这个结点的后缀。所以我们最长公共后缀所表示的结点一定是该结点的某个祖先。
那么两个结点的 lca 就能表示它们的最长公共后缀。因此我们作一个简单的树形 dp 统计有多少对点以根为 lca 即可。

实际上,后缀自动机在翻转的串上建出来的 parent 树,就是原串中的后缀树。

所以后缀树完完全全没什么用啊喂。

@accepted code@

#include<vector>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int MAXN = 500000;
vector<int>G[2*MAXN + 5];
struct sam{
    sam *ch[26], *fa; int mx;
    int tag;
}pl[2*MAXN + 5], *root, *tcnt, *lst;
void init() {
    root = tcnt = lst = &pl[0];
}
sam *newnode(int x) {
    tcnt++; tcnt->tag = x;
    return tcnt;
}
void clone(sam *x, sam *y) {
    for(int i=0;i<26;i++)
        x->ch[i] = y->ch[i];
    x->fa = y->fa;
}
void sam_extend(int x) {
    sam *cur = newnode(1), *p = lst;
    cur->mx = lst->mx + 1; lst = cur;
    while( p && !p->ch[x] )
        p->ch[x] = cur, p = p->fa;
    if( !p )
        cur->fa = root;
    else {
        sam *q = p->ch[x];
        if( p->mx + 1 == q->mx )
            cur->fa = q;
        else {
            sam *nq = newnode(0);
            nq->mx = p->mx + 1;
            clone(nq, q);
            q->fa = cur->fa = nq;
            while( p && p->ch[x] == q )
                p->ch[x] = nq, p = p->fa;
        }
    }
}
int siz[2*MAXN + 5];
char s[MAXN + 5]; ll ans;
void dfs(int rt) {
    siz[rt] = pl[rt].tag;
    for(int i=0;i<G[rt].size();i++) {
        int to = G[rt][i]; dfs(to);
        ans -= 2LL*siz[rt]*siz[to]*pl[rt].mx;
        siz[rt] += siz[to];
    }
}
int main() {
    init(); scanf("%s", s);
    int lens = strlen(s);
    ans = 1LL*(lens-1)*lens*(lens+1)/2;
    for(int i=lens-1;i>=0;i--)
        sam_extend(s[i] - 'a');
    for(int i=1;i<=tcnt-pl;i++) {
        G[pl[i].fa-pl].push_back(i);
    }   
    dfs(0);
    printf("%lld\n", ans);
}

@details@

所以真的想问问大家,后缀树既然可以通过后缀自动机构造出来,时间复杂度空间复杂度也不会更优秀(因为你不可能超过线性复杂度嘛)。

那么后缀树到底用处在哪里?

扫描二维码关注公众号,回复: 4873310 查看本文章

猜你喜欢

转载自www.cnblogs.com/Tiw-Air-OAO/p/10256459.html