【美团杯2020】半前缀计数
题意:定义半前缀是
s
[
1
:
i
]
+
s
[
j
:
k
]
s[1:i]+s[j:k]
s[1:i]+s[j:k], 其中
0
≤
i
<
l
e
n
(
s
)
,
i
<
j
≤
l
e
n
(
s
)
,
j
−
1
≤
k
≤
l
e
n
(
s
)
0≤i<len(s),i<j≤len(s),j−1≤k≤len(s)
0≤i<len(s),i<j≤len(s),j−1≤k≤len(s)。直观上来说,你可以把半前缀理解成某一个前缀 s[1:k] 删除掉某一个子串后形成的结果(当然也允许不删)。
给出字符串 s,你需要求出 s 的所有半前缀中,有多少个不同的字符串。
数据范围:
1
<
=
∣
s
∣
<
=
1
0
6
1<=|s|<=10^6
1<=∣s∣<=106。
题解:对于一个半前缀
s
[
1
:
i
]
+
s
[
j
:
k
]
s[1:i]+s[j:k]
s[1:i]+s[j:k],如果有
s
[
1
:
i
+
1
]
+
s
[
j
+
1
:
k
]
s[1:i+1]+s[j+1:k]
s[1:i+1]+s[j+1:k]与其相等即
s
[
i
+
1
]
=
s
[
j
]
s[i+1] = s[j]
s[i+1]=s[j]时,则说明这个半前缀重复了。因此,对于每个前缀
s
[
1
:
i
]
s[1:i]
s[1:i],我们统计
s
[
i
+
1
:
∣
s
∣
]
s[i+1:|s|]
s[i+1:∣s∣]中有多少不同子串不以
s
[
i
+
1
]
s[i+1]
s[i+1]开始,把这些加起来就是答案。我们将串倒序加入后缀自动机,这样我们就能够知道每次以
s
[
i
]
s[i]
s[i]开始的子串数量。然后对于每个前缀统计答案即可。注意计算空串。
代码:
/*
定义半前缀为s[1...i] + s[j...k]
求本质不同的半前缀数量
思路:如果存在一个半前缀s[1...i] s[j...k] 满足 s[i + 1] = s[j] 说明这是重复的半前缀。
*/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e6 + 7;
struct node{
int pa, son[30], len;
void init(int l) {
memset(son, 0, sizeof(son));
pa = 0;
len = l;
}
}sa[maxn << 1];
int cnt, root, last;
int newnode(int len) {
++cnt;
sa[cnt].init(len);
return cnt;
}
void pre() {
cnt = 0;
root = last = newnode(0);
}
int SAM(int alp) {
int np = newnode(sa[last].len + 1);
int u = last;
while(u && !sa[u].son[alp]) sa[u].son[alp] = np, u = sa[u].pa;
if(!u) sa[np].pa = root;
else {
int v = sa[u].son[alp];
if(sa[v].len == sa[u].len + 1) sa[np].pa = v;
else {
int nv = newnode(sa[u].len + 1);
memcpy(sa[nv].son, sa[v].son, sizeof(sa[v].son));
sa[nv].pa = sa[v].pa; sa[v].pa = sa[np].pa = nv;
while(u && sa[u].son[alp] == v) sa[u].son[alp] = nv, u = sa[u].pa;
}
}
last = np;
return sa[np].len - sa[sa[np].pa].len;
}
char s[maxn];
ll occ[30];
int main() {
scanf("%s", s + 1);
ll ans = 0, sum = 0;
int n = strlen(s + 1);
pre();
for(int i = n; i >= 1; --i) {
int res = SAM(s[i] - 'a');
ans += sum - occ[s[i] - 'a'] + 1;//加上空串
sum += res;
occ[s[i] - 'a'] += res;
}
printf("%lld\n", ans + 1);
return 0;
}