子串拆分
题目链接:ybt高效进阶2-3-4
题目大意
问你在一个字符串中,有多少个 ABA 形的子串。
位置不同其他性质相同的子串算不同子串,位置相同但拆分不同的子串算同一子串。
思路
首先,这个范围可以适用于常数小的 n 2 n^2 n2 做法。
我们如果直接枚举两边范围,再看枚举 A 或者 B 的长度,就会超时。
可以想到看是否能组成 ABA 可以跑一下 KMP,然后看
f
a
i
l
n
fail_n
failn 的值,如果两个区间有相交的,就是不可以的。不过一个字符串可能有很多种搭配方法,而我们上面弄到的只是 A 最长的那一种,那我们要找到其他的,就是继续
i
=
f
a
i
l
i
i=fail_i
i=faili 直到变成
0
0
0,每次得到的都是可以的,只是会越来越短,那你只要一遇到不相交的就可以了。
至于什么时候不相交,如果字符串长度是
n
n
n,就要满足
2
×
f
a
i
l
n
<
n
2\times fail_n< n
2×failn<n。
但是我们最多只能接受
n
2
n^2
n2 的,而你这个是
n
3
n^3
n3。
那我们考虑优化,我们发现我们只要确定左边界,跑完一整个 KMP 的时候,最大的那个会包含所有右边界的情况。
那我们就省去了枚举右边界的,然后就可以了。
代码
#include<cstdio>
#include<cstring>
using namespace std;
int K, n, fail[15001], nn, j, ans;
char s[15001], now[15001];
int main() {
scanf("%s", s + 1);
n = strlen(s + 1);
scanf("%d", &K);
for (int i = 1; i <= n; i++) {//枚举左边界
memset(now, 0, sizeof(now));
for (int k = 1; i + k - 1 <= n; k++)//构造出只有左边界的字符串
now[k] = s[i + k - 1];
memset(fail, 0, sizeof(fail));
nn = strlen(now + 1);
j = 0;
for (int k = 2; k <= nn; k++) {//KMP
while (j && now[k] != now[j + 1]) j = fail[j];
if (now[k] == now[j + 1]) j++;
fail[k] = j;
}
j = 0;
for (int k = 1; k <= nn; k++) {
while (j && now[k] != now[j + 1]) j = fail[j];
if (now[k] == now[j + 1]) j++;
while (j * 2 >= k) j = fail[j];//超过了一半(就是会重叠或中间没有)
if (j >= K) ans++;//满足要求,长度大于等于K
}
}
printf("%d", ans);
return 0;
}