1. 程式人生 > >[Luogu P4248] [BZOJ 3238] [AHOI2013]差異

[Luogu P4248] [BZOJ 3238] [AHOI2013]差異

洛谷傳送門

題目描述

給定一個長度為 n 的字串 S,令 Ti 表示它從第 i 個字元開始的字尾。求

1i<jnlen(Ti)+len(Tj)2×lcp(Ti,Tj)

其中,len(a) 表示字串 a 的長度,lcp(a,b) 表示字串 a 和字串 b 的最長公共字首。

輸入輸出格式

輸入格式:

一行,一個字串 S

輸出格式:

一行,一個整數,表示所求值。

輸入輸出樣例

輸入樣例#1:

cacao

輸出樣例#1:

54

說明

對於 100% 的資料,保證 2n500000,且均為小寫字母。

解題分析

題目求的是字尾的公共字首, 我們把串倒過來, 就變成了求字首的最長公共字尾。

SAM裡每個點都包含了字首, 而顯然其公共字尾就是在parent樹上面的LCA。

所以我們直接在parent樹上從下往上DP就好了。

至於這個玩意

1i<jnlen(Ti)+len(Tj) 顯然就等於(len1)len(len+1)2

程式碼如下:

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#include <cmath>
#include <cctype>
#define R register
#define IN inline
#define W while
#define gc getchar()
#define MX 1000050
#define ll long long
int l, cnt, cur, last;
ll ans, num[MX];
int
to[MX][26], len[MX], par[MX], siz[MX], buc[MX], ord[MX]; char dat[MX]; namespace SAM { IN void insert(R int id) { R int now, tar; cur = ++cnt; len[cur] = len[last] + 1; siz[cur] = 1; for (now = last; ~now; now = par[now]) { if(to[now][id]) break; to[now][id] = cur; } if(now < 0) return last = cur, par[cur] = 0, void(); tar = to[now][id]; if(len[tar] == len[now] + 1) return last = cur, par[cur] = tar, void(); int nw = ++cnt; std::memcpy(to[nw], to[tar], sizeof(to[tar])); par[nw] = par[tar], par[tar] = nw; len[nw] = len[now] + 1; for (; (~now) && to[now][id] == tar; now = par[now]) to[now][id] = nw; par[cur] = nw; last = cur; } IN ll calc() { R int now; ll ret = 0; for (R int i = 1; i <= cnt; ++i) ++buc[len[i]]; for (R int i = 1; i <= l; ++i) buc[i] += buc[i - 1]; for (R int i = 1; i <= cnt; ++i) ord[buc[len[i]]--] = i; for (R int i = cnt; i; --i) { now = ord[i]; if(par[now] > 0) ret += 1ll * siz[now] * siz[par[now]] * len[par[now]], siz[par[now]] += siz[now]; } return ret * 2; } } int main(void) { scanf("%s", dat + 1); l = std::strlen(dat + 1); ans = 1ll * (l - 1) * l / 2 * (l + 1); std::reverse(dat + 1, dat + 1 + l); par[0] = -1; for (R int i = 1; i <= l; ++i) SAM::insert(dat[i] - 'a'); printf("%lld", ans - SAM::calc()); }