题解 P1117 【[NOI2016]优秀的拆分】
提供一个使用 runs 的做法,时间复杂度
这个做法完全不需要 SA 预处理 LCP,用到的最高妙的算法就是二分哈希,但是时间复杂度依然是优秀的一个
前置知识:Lyndon 分解
Lyndon 分解网上资料很多,这部分不在赘述,只补充一部分定义。
-
周期(Period):称
l 是串s 的周期,当且仅当\forall i\in[1,|S|-l],\text{s.t. } s[i]=s[i+l] 。 -
runs:称三元组
r=(i,j,p) 是s 的 run,当且仅当s[i:j] 的最小周期为p ,且2p\le j-i+1 ,且s[i:j] 是极长的一段最小周期为p 的串。 -
Lyndon 串:设
<_\iota 是比较运算符,其中\iota=0/1 ,分别对应两种相反的比较,这里令<_0\Leftrightarrow<,<_1\Leftrightarrow > 。我们称s 是关于<_\iota 的 Lyndon 串,当且仅当\forall i\in [2,|s|], s<_\iota s[i:|s|] 。 -
Lyndon 分解:称
a_1a_2...a_n=s 是s 关于<_\iota 的 Lyndon 分解,当且仅当所有a_i 均是关于<_\iota 的 Lyndon 串,且a_i\not<_\iota a_{i+1} 。可以证明,一个串的 Lyndon 分解存在且唯一。 -
Lyndon Root:称
\lambda=s[i_\lambda:j_\lambda] 是一个 runr=(i,j,p) 关于<_\iota 的 Lyndon Root,当且仅当[i_\lambda:j_\lambda]\subseteq [i,j] ,且\lambda 是一个 Lyndon 串。
考虑如何求出一个串的所有 run。
我们把串 Lyndon 分解,然后枚举一个
我们可以二分哈希找到包含
如果对
然后考虑计算“以某个位置结尾的平方串的数量”和“以某个位置开头的平方串的数量”,显然一个平方串会自然地对应一个 run 的子串。
The Runs Theorem 指出,一个串的 run 的数量不超过串长,那么我们接下来只要枚举一个 run,然后枚举最小周期的倍数
可以证明,这一部分的复杂度不超过
参考实现:
#include<bits/stdc++.h>
using namespace std;
#define pb push_back
#define LL long long
const int CN = 3e4 + 10;
const int P = 191011109;
const int B = 39; int pB[CN];
int read(){
int s = 0, ne = 1; char c = getchar();
for(;c < '0' || c > '9';c = getchar()) if(c == '-') ne = -1;
for(;c >= '0' && c <= '9';c = getchar()) s = (s << 1) + (s << 3) + c - '0';
return s * ne;
}
int add(int x, int y) {return x + y >= P ? x + y - P : x + y;}
int ha[CN];
int get(int l, int r) {return add(ha[r], P - (1ll * ha[l - 1] * pB[r - l + 1] % P));}
int T, n; char ch[CN];
int LCP(int l1, int r1, int l2, int r2){
if(ch[l1] != ch[l2]) return 0;
int l = 1, r = min(r1 - l1 + 1, r2 - l2 + 1);
while(l < r){
int mid = (l + r + 1) >> 1;
if(get(l1, l1 + mid - 1) == get(l2, l2 + mid - 1)) l = mid;
else r = mid - 1;
}
return l;
}
int LCS(int l1, int r1, int l2, int r2){
if(ch[r1] != ch[r2]) return 0;
int l = 1, r = min(r1 - l1 + 1, r2 - l2 + 1);
while(l < r){
int mid = (l + r + 1) >> 1;
if(get(r1 - mid + 1, r1) == get(r2 - mid + 1, r2)) l = mid;
else r = mid - 1;
}
return l;
}
bool le(int l1, int r1, int l2, int r2){
if(l1 == l2) return r1 < r2;
int l = LCP(l1, r1, l2, r2);
if(l1 + l > r1 || l2 + l > r2) return r1 - l1 < r2 - l2;
return ch[l1 + l] < ch[l2 + l];
}
class PAIR{
public: int l, r, p;
bool operator < (const PAIR &o) const{
return l ^ o.l ? l < o.l : (r ^ o.r ? p < o.p : r < o.r);
}
bool operator == (const PAIR &o) const{
return l == o.l && r == o.r;
}
} ;
PAIR mp(int a, int b, int c) {PAIR o; o.l = a, o.r = b, o.p = c; return o;}
vector<PAIR> runs; int stk[CN], ed[CN], top;
void lyndon(){
top = 0;
for(int i = n; i; i--){
stk[++top] = i;
while(top > 1 && le(i, stk[top], stk[top] + 1, stk[top - 1])) top--;
ed[i] = stk[top];
}
for(int i = 1; i <= n; i++){
int j = ed[i], lcs = LCS(1, i - 1, 1, j), lcp = LCP(i, n, j + 1, n), l, r;
l = i - lcs, r = j + lcp;
if((r - l + 1) / (j - i + 1) > 1) runs.pb(mp(l, r, j - i + 1));
}
}
int f[CN], g[CN];
int main()
{
// freopen("_in.in", "r", stdin);
n = 3e4, pB[0] = 1;
for(int i = 1; i <= n; i++) pB[i] = 1ll * pB[i - 1] * B % P;
T = read();
while(T--){
scanf("%s", ch + 1), n = strlen(ch + 1);
for(int i = 1; i <= n; i++) ha[i] = add(1ll * B * ha[i - 1] % P, ch[i] - 'a');
runs.clear();
lyndon();
for(int i = 1; i <= n; i++) ch[i] = 'a' + 'z' - ch[i];
lyndon();
sort(runs.begin(), runs.end());
int len = unique(runs.begin(), runs.end()) - runs.begin();
for(int i = 0; i < len; i++){
int l = runs[i].l, r = runs[i].r, p = runs[i].p;
for(int L = p + p; L <= r - l + 1; L += p + p){
g[l]++, g[r - L + 2]--;
f[l + L - 1]++, f[r + 1]--;
}
}
for(int i = 1; i <= n; i++) f[i] += f[i - 1], g[i] += g[i - 1];
LL ans = 0;
for(int i = 1; i < n; i++) ans += 1ll * f[i] * g[i + 1];
printf("%lld\n", ans);
for(int i = 0; i <= n + 1; i++) f[i] = g[i] = 0;
}
return 0;
}