CodeForces 526D Om Nom and Necklace
思路
题意相当于将 \(S\) 表示成 \(A^kB\)(\(A^x = A^{x-1}A\),\(A^0\) 为空串),其中 \(B\) 为 \(A\) 的前缀。
考虑枚举 \(|A^k|\),设 \(|A^k| = len\ (k\ |\ len)\),在 \([1,len]\) 中寻找长度为 \(\dfrac{len}{k}\) 的循环节。看到循环节就想到 KMP,预处理出 \(fail_i\),那么 \([1,len]\) 的最短的循环节长度为 \(len - fail_{len}\)。当 \(len - fail_{len} \nmid len\) 或者 \(len < 2 \cdot (len - fail_{len})\)(即循环次数 \(< 2\);注意当 \(m=1\) 时这个条件可以不满足),则 \([1,len]\) 不存在循环节。现在已经知道了最短的循环节长度为 \(len - fail_{len}\),若 \([1,len]\) 存在长度为 \(\dfrac{len}{k}\) 的循环节,那么必须满足 \((len - fail_{len})\ |\ \dfrac{len}{k}\)。
现在要求最长的满足 \(B\) 为 \(A\) 前缀的 \(B\) 的长度。先求出原串的 \(\mathbf{Z}\) 函数数组 \(z\)。显然 \(B\) 也必须是整个串的前缀,所以 \(|B| \le z_{len + 1}\)。又因为 \(|B| \le |A| = \dfrac{len}{k}\),所以 \(|B|_{\max} = \min(z_{len+1},\dfrac{len}{k})\)。知道了 \(|B|\) 的范围,也就知道了满足条件的整个串的前缀的范围。开一个数组 \(d_i\),若 \(d_i > 0\) 则原串前缀 \([1,i]\) 满足条件,否则不满足,在 \(d\) 数组上将 \([i,i+|B|_{\max}]\) 整体加一。实现时差分即可。
时空复杂度均为 \(O(n)\)。
代码
code
/*
p_b_p_b txdy
AThousandMoon txdy
AThousandSuns txdy
hxy txdy
*/
#include <bits/stdc++.h>
#define pb push_back
#define fst first
#define scd second
using namespace std;
typedef long long ll;
typedef pair<ll, ll> pii;
const int maxn = 1000100;
int n, m, nxt[maxn], fail[maxn], d[maxn];
char s[maxn];
void solve() {
scanf("%d%d%s", &n, &m, s + 1);
nxt[1] = n;
for (int i = 2, l = 0, r = 0; i <= n; ++i) {
nxt[i] = (i > r) ? 0 : min(nxt[i - l + 1], r - i + 1);
while (s[nxt[i] + 1] == s[nxt[i] + i]) {
++nxt[i];
}
if (i + nxt[i] - 1 > r) {
l = i;
r = i + nxt[i] - 1;
}
}
for (int i = 2, j = 0; i <= n; ++i) {
while (j && s[i] != s[j + 1]) {
j = fail[j];
}
if (s[i] == s[j + 1]) {
++j;
}
fail[i] = j;
}
for (int i = m; i <= n; i += m) {
// printf("fail: %d\n", i - fail[i]);
if (i % (i - fail[i]) || (i - fail[i]) * min(m, 2) > i || (i / m) % (i - fail[i])) {
continue;
}
++d[i];
--d[i + min(i / m, nxt[i + 1]) + 1];
// printf("%d %d\n", i / m, nxt[i + 1]);
}
for (int i = 1; i <= n; ++i) {
d[i] += d[i - 1];
printf("%d", min(d[i], 1));
}
}
int main() {
int T = 1;
// scanf("%d", &T);
while (T--) {
solve();
}
return 0;
}