[NOI2016]優秀的拆分
分析
這道題不管採用Hash,後綴數組還是自動機,網上大部分的題解都採用了關鍵點+調和級數這個操作。本蒟蒻想不到關鍵點這個操作,所以採用的是一種較爲繁瑣的做法。
首先肯定將問題轉化成對於每個求以爲邊界的結構個數,當然前綴後綴分別求一遍,以下默認是前綴。
考慮形式化這個問題,對於某個前綴,求所有的前綴,使得的最長公共後綴的長度大於,也就是
對於後綴前綴的問題,我們一般將他們放到後綴自動機的樹上考慮,由於後綴自動機的樹相當於是將每個前綴逆序插入,所以某兩個前綴的對應的就是他們樹上的。
所以轉化成樹上給若干個關鍵點(對應的是字符串的前綴),對於每個,求。
考慮採用樹上啓發式合併,對於每個節點建立一顆動態開點線段樹,我們讓父親繼承重兒子的線段樹,把其他子樹中的線段樹的節點暴力插入合併。
插入某個節點的時候,考慮線段樹內的節點對其的貢獻,和它對線段樹內節點的貢獻。前者用一個區間詢問即可,否則在線段樹上打標記,但這個線段樹要被拆開的時候再把暴力標記推下去貢獻到答案上即可。
每個節點插入的之後其所在線段樹大小翻倍,所以之多插入次,總複雜度
代碼
#include<bits/stdc++.h>
const int N = 6e4 + 10, T = 1e6 + 10;
int ri() {
char c = getchar(); int x = 0, f = 1; for(;c < '0' || c > '9'; c = getchar()) if(c == '-') f = -1;
for(;c >= '0' && c <= '9'; c = getchar()) x = (x << 1) + (x << 3) - '0' + c; return x * f;
}
int nx[N], pr[N], fa[N], ch[N][26], st[N], rt[N], mx[N], ans1[N], ans2[N];
int ls[T], rs[T], cnt[T], tag[T], tp, *tot, top, n, last, sz;
bool val[N]; char s[N];
void Clear() {
top = last = 1; pr[1] = 0;
memset(ch[1], 0, sizeof(ch[1]));
sz = 0;
for(int i = 1;i <= n; ++i)
tot[i] = 0;
}
void Push(int p) {
if(tag[p]) {
if(ls[p])
tag[ls[p]] += tag[p];
if(rs[p])
tag[rs[p]] += tag[p];
tag[p] = 0;
}
}
void Get(int p, int L, int R) {
if(L == R) {
tot[L] += tag[p];
st[++tp] = L;
return ;
}
int m = L + R >> 1; Push(p);
if(ls[p])
Get(ls[p], L, m);
if(rs[p])
Get(rs[p], m + 1, R);
}
void Ins(int &p, int L, int R, int x) {
if(!p) {p = ++sz; ls[p] = rs[p] = tag[p] = cnt[p] = 0;}
++cnt[p]; if(L == R) return ;
int m = L + R >> 1; Push(p);
if(x <= m) Ins(ls[p], L, m, x);
else Ins(rs[p], m + 1, R, x);
}
void Modify(int p, int L, int R, int st, int ed) {
if(L == st && ed == R)
return ++tag[p], void();
int m = L + R >> 1; Push(p);
if(st <= m && ls[p])
Modify(ls[p], L, m, st, std::min(ed, m));
if(ed > m && rs[p])
Modify(rs[p], m + 1, R, std::max(st, m + 1), ed);
}
int Query(int p, int L, int R, int st, int ed) {
if(L == st && ed == R)
return cnt[p];
int m = L + R >> 1, ans = 0;
if(st <= m && ls[p])
ans += Query(ls[p], L, m, st, std::min(ed, m));
if(ed > m && rs[p])
ans += Query(rs[p], m + 1, R, std::max(st, m + 1), ed);
return ans;
}
void Extend(int c) {
int p = last, np = last = ++top;
memset(ch[np], 0, sizeof(ch[np])); pr[np] = 0;
mx[np] = mx[p] + 1; rt[np] = 0; val[np] = true;
for(;p && !ch[p][c]; p = fa[p])
ch[p][c] = np;
if(!p) fa[np] = 1;
else {
int q = ch[p][c];
if(mx[q] == mx[p] + 1)
fa[np] = q;
else {
int nq = ++top; mx[nq] = mx[p] + 1;
memcpy(ch[nq], ch[q], sizeof(ch[nq]));
rt[nq] = 0; val[nq] = false; pr[nq] = 0;
fa[nq] = fa[q];
fa[q] = fa[np] = nq;
for(;ch[p][c] == q; p = fa[p])
ch[p][c] = nq;
}
}
}
void Merge(int rt, int x, int c) {
if(x - c)
tot[x] += Query(rt, 1, n, x - c, x);
Modify(rt, 1, n, x, std::min(n, x + c));
}
void Dfs(int u) {
if(!pr[u])
return Ins(rt[u], 1, n, mx[u]);
int ds = 0;
for(int i = pr[u]; i; i = nx[i]) {
Dfs(i);
if(cnt[rt[i]] > cnt[rt[ds]])
ds = i;
}
rt[u] = rt[ds];
for(int i = pr[u]; i; i = nx[i])
if(i != ds){
tp = 0; Get(rt[i], 1, n);
for(int x = 1; x <= tp; ++x)
Merge(rt[u], st[x], mx[u]);
for(int x = 1; x <= tp; ++x)
Ins(rt[u], 1, n, st[x]);
}
if(val[u]) {
Merge(rt[u], mx[u], mx[u]);
Ins(rt[u], 1, n, mx[u]);
}
}
void Work() {
Clear();
for(int i = 1; i <= n; ++i)
Extend(s[i] - 'a');
for(int i = 2;i <= top; ++i)
nx[i] = pr[fa[i]], pr[fa[i]] = i;
Dfs(1); tp = 0; Get(rt[1], 1, n);
}
int main() {
for(int T = ri(); T--; ) {
scanf("%s", s + 1); n = strlen(s + 1);
tot = ans1; Work();
std::reverse(s + 1, s + n + 1);
tot = ans2; Work();
long long ans = 0;
for(int i = 1;i <= n; ++i)
ans += 1LL * ans1[i] * ans2[n - i];
printf("%lld\n", ans);
}
return 0;
}