2022 牛客 多校 D Mi Re Do Si La? So Fa!

后缀数组


题意

给定长为 nn 的小写字符串 ss ,求所有子串的循环节的长度和。
其中 1n1061 \leq n \leq 10^6

思路

题意可以转换为找形如 XX...XXX...X 的子串的个数,子串的贡献为循环节 XX 的长度。
考虑枚举 XX 的长度 lenlen ,对于循环节即为它本身的子串,我们直接加上贡献,对于循环节出现次数大于等于 22 的子串,考虑用类似P1117 [NOI2016] 优秀的拆分的套路,在原串上每距离 lenlen 处设立一个标记点,第 ii 个记为 pip_i ,若一个子串包含了 k(k2)k (k \geq 2) 个循环节,则它一定经过恰好 kk 个标记点 pl,pl+1,...,prp_l, p_{l+1}, ..., p_r 。考虑枚举 plp_l ,统计左端点在区间 [pllen+1][p_l - len + 1] 内的子串的贡献。具体的,求出原串以 plp_lpl+1p_{l+1} 为起点 / 终点的 lcplcplcslcs ,合法的起点个数即为 min(lcs,len)min(lcs, len) ,对于 k3k \geq 3 的情况即为 lcp>lenlcp > len 的部分,这一部分子串的数量即为 lcplen\lfloor \frac{lcp}{len} \rfloor ,对于 k=2k = 2 的情况,将 lcplcplenlen 取模后直接计算 lcplcplcslcs 的重合部分即可。
总复杂度 O(nlogn)O(n \cdot logn)

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
/*
Author: Cupids_Bow
Time: 2022-08-20 19:44:13
*/
#include<bits/stdc++.h>
using namespace std;

using ll=long long;

constexpr int N=2e6+5;
int _=1;

int n;
ll ans;
struct SA{
int n,m;
char s[N];
int sa[N],rk[N],height[N],h[N];
int minn[21][N],lg[N];
void init(int _n,int _m){
n=_n;
m=_m;
}
void buildsa(){
static int oldrk[N<<1],id[N],px[N],cnt[N];
int i,p,w;
for(i=1;i<=m+1;i++) cnt[i]=0;
for(i=1;i<=n+1;++i) sa[i]=rk[i]=id[i]=px[i]=0;
for(i=1;i<=n*2;i++) oldrk[i]=0;
for(i=1;i<=n;++i) ++cnt[rk[i]=(s[i]-'a'+1)];
for(i=1;i<=m;++i) cnt[i]+=cnt[i-1];
for(i=n;i>=1;--i) sa[cnt[rk[i]]--]=i;
for(w=1;;w<<=1,m=p){
for(p=0,i=n;i>n-w;--i) id[++p]=i;
for(i=1;i<=n;++i)
if(sa[i]>w) id[++p]=sa[i]-w;
for(i=1;i<=m;i++) cnt[i]=0;
for(i=1;i<=n;++i) ++cnt[px[i]=rk[id[i]]];
for(i=1;i<=m;++i) cnt[i]+=cnt[i-1];
for(i=n;i>=1;--i) sa[cnt[px[i]]--]=id[i];
for(i=1;i<=n;i++) oldrk[i]=rk[i];
for(p=0,i=1;i<=n;++i)
rk[sa[i]]=(oldrk[sa[i]]==oldrk[sa[i-1]]&&oldrk[sa[i]+w]==oldrk[sa[i-1]+w])?p:++p;
if(p==n){
for(i=1;i<=n;++i) sa[rk[i]]=i;
break;
}
}
}
void buildheight(){
for(int i=1;i<=n+1;++i) height[i]=h[i]=0;
int k=0;
for(int i=1;i<=n;i++){
if(rk[i]==1) continue;
if(k) --k;
int j=sa[rk[i]-1];
while(j+k<=n&&i+k<=n&&s[i+k]==s[j+k]) k++;
height[rk[i]]=k;
}
for(int i=1;i<=n;i++) h[i]=height[rk[i]];
for(int i=2;i<=n;i++) lg[i]=lg[i>>1]+1;
for(int i=1;i<=n;i++) minn[0][i]=height[i];
for(int j=1;(1<<j)<=n;j++)
for(int i=1;i+(1<<j)-1<=n;i++) minn[j][i]=min(minn[j-1][i],minn[j-1][i+(1<<(j-1))]);
}
int get_lcp(int l,int r){
if(l==r) return n-l+1;
l=rk[l],r=rk[r];
if(l>r) swap(l,r);
l++;
int k=lg[r-l+1];
return min(minn[k][l],minn[k][r-(1<<k)+1]);
}
}sa;

int get_lcp(int x,int y){
if(x<1||x>n||y<1||y>n) return 0;
return sa.get_lcp(x,y);
}

int get_lcs(int x,int y){
if(x<1||x>n||y<1||y>n) return 0;
x=n-x+1+n+1;
y=n-y+1+n+1;
return sa.get_lcp(x,y);
}

ll calc(int len){
ll res=1ll*len*(n-len+1);
for(int i=len;i+len<=n;i+=len){
int x=min(get_lcs(i,i+len),len);
int y=get_lcp(i+1,i+1+len);
ll cnt=1ll*x*(y/len);
y%=len;
cnt+=max(0,y-(len-x)+1);
res+=cnt*len;
}
return res;
}

void work(){
ans=0;
cin>>sa.s+1;
n=strlen(sa.s+1);
sa.s[n+1]=('z'+1);
for(int i=1;i<=n;i++) sa.s[i+n+1]=sa.s[n-i+1];
sa.init(n*2+1,27);
sa.buildsa();
sa.buildheight();
for(int i=1;i<=n;i++) ans+=calc(i);
cout<<ans<<"\n";
}

int main(){
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);

cin>>_;
while(_--){
work();
}

return true&&false;
}