洛谷P2375 [NOI2014] 动物园【题解】
题目简要
对于字符串 \(𝑆\) 的每个前缀 \(𝑆[1 ⋯ 𝑖]\),计算出它有多少个 \(border\),满足这些 \(border\) 的长度都不超过该前缀长度的一半。
浅浅谈一下?
至于思路怎么来的,我也不知道?
先考虑一个引论:
\(a\) 是 \(S[1…i]\) 的 \(border\),且满足 \(a\le\frac{i}{2}\),那么一定存在 \(a-1\) 是 \(S[1…i-1]\) 的 \(border\),且同样满足 \(a-1<\frac{i-1}{2}\)。
证明:
-
首先按照 \(border\) 的性质,\(a - 1\) 必然是 \(S[1…i-1]\) 的 \(border\)。(如果不懂请手动画图)
-
有 \(a<=\frac{i}{2}\),所以有 \(a-1 \le \frac{i-2}{2}\),就有 \(a-1 < \frac{i-1}{2}\)。
通过这个引论,我们可以再得到一个结论。
-
如果存在 \(a\) 符合上面的性质,那么有 \(a-1\) 符合上面的性质。
-
但是如果存在 \(a-1\) 符合上面的性质,不一定有 \(a\) 符合上面的性质。
这样,求 \(S[1...i]\) 时,我们就可以通过枚举 \(S[1...i-1]\) 中符合上面性质的 \(a\) 来看看是否满足上面的性质。
怎么枚举 \(S[1...i-1]\) 中符合上面性质的 \(a\) 呢?
我们记 \(a_0\) 为 \(S[1...i-1]\) 的最长的符合上面性质的 \(border\)。
因为 \(KMP\) 问题中有个结论:次长的 \(border\) = \(Next[x]\),其中 \(x\) 为最长的 \(border\) 。
所以,我们就可以递归枚举 \(S[1...i-1]\) 中符合上面性质的 \(a\)。
于是代码就出来了
Code:
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1e6 + 7, Mod = 1e9 + 7;
int T, N[MAXN], A[MAXN], D[MAXN], ans = 1, n;
char S[MAXN];
int main () {
for (cin >> T; T; T --) {
cin >> S + 1; n = strlen(S + 1);
N[1] = 0, D[0] = 0, D[1] = 1; ans = 1;
for (int i = 2; i <= n; i ++) {
int p = N[i - 1]; N[i] = 0;
while (S[p + 1] != S[i] && p != 0) p = N[p];
if (S[p + 1] == S[i]) N[i] = p + 1;
D[i] = D[N[i]] + 1;
}
memset(A, 0, sizeof(A));
for (int i = 1; i <= n; i ++) {
int p = A[i - 1];
while ((S[p + 1] != S[i] || p * 2 + 2 > i) && p != 0) p = N[p];
if (S[p + 1] == S[i] && p * 2 + 2 <= i) A[i] = p + 1;
ans = 1ll * ans * (D[A[i]] + 1) % Mod;
}
cout << ans << "\n";
}
return 0;
}
完结撒花✿✿ヽ(°▽°)ノ✿