「AHOI2013」 差異
知識點: SA,線段樹,單調棧
原題面 Loj Luogu
題意簡述
給定一長度為 \(n\) 的字串 \(S\),令 \(T_i\) 表示從第 \(i\) 個字元開始的子串,求:
\[\sum_{1\le i<j\le n}\{\operatorname{len}(T_i) +\operatorname{len}(T_j) - 2\times \operatorname{lcp} (T_i,T_j)\} \]
\(\operatorname{len}(a)\) 表示字串 \(a\) 的長度,\(\operatorname{lcp}(a,b)\) 表示字串 \(a,b\) 的最長公共字首。
分析題意
SA
化下式子:
\[\begin{aligned} ans &= \sum_{1\le i<j\le n}\{\operatorname{len}(T_i) +\operatorname{len}(T_j) - 2\times \operatorname{lcp} (T_i,T_j)\}\\ &= \sum_{1\le i<j\le n}\{(i +j) - 2\times \operatorname{lcp} (T_i,T_j)\}\\ &= \dfrac{(n-1)\times n \times (n+1)}{2} + 2\sum_{1\le i<j\le n}\operatorname{lcp} (T_i,T_j) \end{aligned}\]
考慮如何快速求後一半,即所有 \(\operatorname{lcp}\) 之和。
發現有下列等價關係:
\[\sum_{1\le i<j\le n}\operatorname{lcp} (T_i,T_j) = \sum_{1\le i<j\le n}\operatorname{lcp}(T_{sa_i}, T_{sa_j}) \]
\(\operatorname{lcp}(a,b) = \operatorname{lcp}(b,a)\),列舉 \(sa\) 一定不會重也不會漏。
類似這題的套路:「HAOI2016」找相同字元,
考慮列舉 \(sa_j\),用權值線段樹維護 \(sa_i (i<j)\)
引理:\(\forall 1\le i < j\le n,\, \operatorname{lcp}(sa_i,sa_j) = \min\limits_{k=i+1}^j\{\operatorname{height_k}\}\)。
模擬引理,當 \(j+1\) 時將權值線段樹中所有 \(>\operatorname{height}_{j+1}\) 的元素刪除,並新增相同個數個 元素 \(\operatorname{height}_{j+1}\)。
新增一個 \(\operatorname{height}_{j+1}\),代表新增的 \(sa_j\) 的貢獻。
貢獻求和即可。
總複雜度 \(O(n\log n)\)。
線段樹太傻逼了,考慮單調棧。
發現有下列等價關係:
\[\sum_{1\le i<j\le n}\operatorname{lcp}(T_{sa_i}, T_{sa_j}) = \sum_{1\le i<j\le n}\min_{k=i+1}^{j}\{\operatorname{height}_k\} \]
即求 \(\operatorname{height}\) 每個區間的區間最小值之和。
經典問題,考慮 \(\operatorname{height}\) 作為最小值的區間的最大 左/右端 點,可單調棧維護。
答案即 \(\sum\limits_{i=2}^{n}(i-l_i)\times (r_i-i)\times \operatorname{height}_i\)。
注意區間長度不能為 1。
字尾樹
考慮原始式子:
\[\sum_{1\le i<j\le n}\{\operatorname{len}(T_i) +\operatorname{len}(T_j) - 2\times \operatorname{lcp} (T_i,T_j)\} \]
這玩意長得很樹上差分。
對於 \(S\) 的字尾樹,\(\operatorname{lcp}\) 即為字尾樹的 \(\operatorname{lca}\)。
上式等價於後綴樹上所有後綴之間的距離。
對反串建 SAM,即得字尾樹。
題目轉化為:樹上某一點是多少 表示字尾的節點 的 \(\operatorname{lca}\) 再乘上 \(dep\)。
記錄子樹大小, DP 實現即可。
爆零小技巧:線段樹不一定只開 4 倍空間,當 \(n\) 到達 \(5\times 10^5\) 級別一定要小心。
程式碼實現
SA + 單調棧
這寫法挺神仙的,感覺要重學單調棧。
//
/*
By:Luckyblock
*/
#include <cstdio>
#include <ctype.h>
#include <cstring>
#include <iostream>
#include <algorithm>
#define ll long long
const int kMaxn = 5e5 + 10;
//=============================================================
char S[kMaxn];
int n, m, sa[kMaxn], rk[kMaxn << 1], oldrk[kMaxn << 1], height[kMaxn];
int cnt[kMaxn], id[kMaxn], rkid[kMaxn];
int top, st[kMaxn], l[kMaxn], r[kMaxn];
//=============================================================
inline int read() {
int f = 1, w = 0; char ch = getchar();
for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void GetMax(int &fir, int sec) {
if (sec > fir) fir = sec;
}
void GetMin(int &fir, int sec) {
if (sec < fir) fir = sec;
}
int cmp(int x, int y, int w) {
return oldrk[x] == oldrk[y] &&
oldrk[x + w] == oldrk[y + w];
}
void GetHeight() {
for (int i = 1, k = 0; i <= n; ++ i) {
if (rk[i] == 1) k = 0;
else {
if (k > 0) k --;
int j = sa[rk[i] - 1];
while (i + k <= n && j + k <= n &&
S[i + k] == S[j + k]) {
++ k;
}
}
height[rk[i]] = k;
}
}
void SuffixSort() {
n = strlen(S + 1);
m = 1010;
for (int i = 1; i <= n; ++ i) cnt[rk[i] = S[i]] ++;
for (int i = 1; i <= m; ++ i) cnt[i] += cnt[i - 1];
for (int i = n; i; -- i) sa[cnt[rk[i]] --] = i;
for (int p, w = 1; w < n; w <<= 1) {
p = 0;
for (int i = n; i > n - w; -- i) id[++ p] = i;
for (int i = 1; i <= n; ++ i) {
if (sa[i] > w) id[++ p] = sa[i] - w;
}
memset(cnt, 0, sizeof (cnt));
for (int i = 1; i <= n; ++ i) cnt[rkid[i] = rk[id[i]]] ++;
for (int i = 1; i <= m; ++ i) cnt[i] += cnt[i - 1];
for (int i = n; i; -- i) sa[cnt[rkid[i]] --] = id[i];
std :: swap(rk, oldrk);
m = 0;
for (int i = 1; i <= n; ++ i) {
m += (cmp(sa[i], sa[i - 1], w) ^ 1);
rk[sa[i]] = m;
}
}
GetHeight();
}
//=============================================================
int main() {
scanf("%s", S + 1);
SuffixSort();
ll ans = 1ll * ((n - 1ll) * n / 2ll) * (n + 1ll) ;
st[(top = 1)] = 1;
for (int i = 2; i <= n; ++ i) {
while (top && height[st[top]] > height[i]) {
r[st[top]] = i;
top --;
}
l[i] = st[top];
st[++ top] = i;
}
while (top) r[st[top --]] = n + 1;
for (int i = 2; i <= n; ++ i) {
ans -= 2ll * (i - l[i]) * (r[i] - i) * height[i];
}
printf("%lld", ans);
return 0;
}
SA + 線段樹
//知識點:SA
/*
By:Luckyblock
*/
#include <cstdio>
#include <ctype.h>
#include <cstring>
#include <iostream>
#include <algorithm>
#define ll long long
#define lson (now_<<1)
#define rson (now_<<1|1)
const int kMaxn = 5e5 + 10;
//=============================================================
char S[kMaxn];
int n, m, sa[kMaxn], rk[kMaxn << 1], oldrk[kMaxn << 1], height[kMaxn];
int cnt[kMaxn], id[kMaxn], rkid[kMaxn];
ll size[kMaxn << 3], sum[kMaxn << 3];
bool tag[kMaxn << 3];
//=============================================================
inline int read() {
int f = 1, w = 0; char ch = getchar();
for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void GetMax(int &fir, int sec) {
if (sec > fir) fir = sec;
}
void GetMin(int &fir, int sec) {
if (sec < fir) fir = sec;
}
int cmp(int x, int y, int w) {
return oldrk[x] == oldrk[y] &&
oldrk[x + w] == oldrk[y + w];
}
void GetHeight() {
for (int i = 1, k = 0; i <= n; ++ i) {
if (rk[i] == 1) k = 0;
else {
if (k > 0) k --;
int j = sa[rk[i] - 1];
while (i + k <= n && j + k <= n &&
S[i + k] == S[j + k]) {
++ k;
}
}
height[rk[i]] = k;
}
}
void SuffixSort() {
n = strlen(S + 1);
m = 1010;
for (int i = 1; i <= n; ++ i) cnt[rk[i] = S[i]] ++;
for (int i = 1; i <= m; ++ i) cnt[i] += cnt[i - 1];
for (int i = n; i; -- i) sa[cnt[rk[i]] --] = i;
for (int p, w = 1; w < n; w <<= 1) {
p = 0;
for (int i = n; i > n - w; -- i) id[++ p] = i;
for (int i = 1; i <= n; ++ i) {
if (sa[i] > w) id[++ p] = sa[i] - w;
}
memset(cnt, 0, sizeof (cnt));
for (int i = 1; i <= n; ++ i) cnt[rkid[i] = rk[id[i]]] ++;
for (int i = 1; i <= m; ++ i) cnt[i] += cnt[i - 1];
for (int i = n; i; -- i) sa[cnt[rkid[i]] --] = id[i];
std :: swap(rk, oldrk);
m = 0;
for (int i = 1; i <= n; ++ i) {
m += (cmp(sa[i], sa[i - 1], w) ^ 1);
rk[sa[i]] = m;
}
}
GetHeight();
}
void Pushdown(int now_) {
tag[lson] = tag[rson] = true;
size[lson] = size[rson] = 0;
sum[lson] = sum[rson] = 0;
tag[now_] = false;
}
void Pushup(int now_) {
size[now_] = size[lson] + size[rson];
sum[now_] = sum[lson] + sum[rson];
}
ll Delete(int now_, int L_, int R_, int ql_, int qr_) {
if (ql_ <= L_ && R_ <= qr_) {
ll ret = size[now_];
tag[now_] = true;
size[now_] = sum[now_] = 0ll;
return ret;
}
if(tag[now_]) Pushdown(now_);
int mid = (L_ + R_) >> 1;
ll ret = 0ll;
if (ql_ <= mid) ret += Delete(lson, L_, mid, ql_, qr_);
if (qr_ > mid) ret += Delete(rson, mid + 1, R_, ql_, qr_);
Pushup(now_);
return ret;
}
void Insert(int now_, int L_, int R_, int pos_, ll num) {
if (! num) return ;
if (L_ == R_) {
size[now_] += num;
sum[now_] += 1ll * num * (L_ - 1ll);
return ;
}
if (tag[now_]) Pushdown(now_);
int mid = (L_ + R_) >> 1;
if (pos_ <= mid) Insert(lson, L_, mid, pos_, num);
else Insert(rson, mid + 1, R_, pos_, num);
Pushup(now_);
}
//=============================================================
int main() {
scanf("%s", S + 1);
SuffixSort();
ll ans = 1ll * ((n - 1ll) * n / 2ll) * (n + 1ll) ;
for (int j = 2; j <= n; ++ j) {
ll num = Delete(1, 1, n + 1, height[j] + 2, n + 1);
Insert(1, 1, n + 1, height[j] + 1, num + 1);
ans -= 2ll * sum[1];
}
printf("%lld", ans);
return 0;
}
字尾樹
咕咕咕,建議 Lg題解。