1. 程式人生 > >@bzoj - [email protected] [Ahoi2013]差異

@bzoj - [email protected] [Ahoi2013]差異

目錄


@[email protected]

給定一個長度為 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

@[email protected]

那個式子我們可以分兩部分求解: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 [email protected]

#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);
}

@[email protected]

所以真的想問問大家,字尾樹既然可以通過後綴自動機構造出來,時間複雜度空間複雜度也不會更優秀(因為你不可能超過線性複雜度嘛)。

那麼字尾樹到底用處在哪裡?