L. Lazy Printing
https://codeforces.com/gym/104252/problem/C
Vinícius has an interesting typing machine. The machine accepts instructions consisting of a non-empty string s and a positive integer n. For each such instruction, the machine prints n characters: the i-th (0-based) printed character equals s_r, where r is the remainder after dividing i by the length of s and s_r denotes the r-th (0-based) character of s. For instance, with the sequence of instructions:
- s=“ab”, n=4
- s=“cd”, n=3
- s=“xx”, n=2
the machine will print “ababcdcxx”.
Vinícius is lazy, so he only gives strings of length at most D to the machine in each instruction. Since he is very lazy, he also wants to use as few instructions as possible. Given a string T and the integer D, help Vinícius find the minimum number of instructions he needs in order to print T using the machine.
Input
The input consists of a single line that contains a string T of lowercase letters followed by the integer D ( 1 ≤ D ≤ ∣ T ∣ ≤ 2 × 1 0 5 ) (1 \leq D \leq |T| \leq 2 \times 10^5) (1≤D≤∣T∣≤2×105), as described in the statement.
Output
Output a single line with an integer indicating the minimum number of instructions Vinícius needs.
Examples
input
ababcdcxx 2
output
3
input
aaabbcd 1
output
4
input
abcabca 3
output
1
题意:给一个总字符串,然后把他拆成若干个子串,每个子串由自己的循环节循环得来(不一定是完整周期),题目要求循环节的长度最长是d,求最少拆成几个子串。比如样例三,abc作为循环节,循环得到abcabca,就是原串,所以答案是1,样例1:a作为循环节循环三次变成aaa,b作为循环节循环两次变为bb,c,d作为循环节只循环一次得到c,d,组合起来就是aaabbcd,答案是4.
本题主要问题是贪心思想,方法有很多;只要能证明,从 0... l e n − 1 0...len-1 0...len−1枚举,某个长度不大于d的循环节循环得到的最长子串就是拆分的局部最优解。也就是从左到右枚举s[i],从i开始再枚举循环节的长度k(范围1到d),再找 s [ i , l e n − 1 ] s[i,len-1] s[i,len−1]和 s [ i + k , l e n − 1 ] s[i+k,len-1] s[i+k,len−1]的最长公共前缀lcp,此时这个子串的长度就是 k + l c p k+lcp k+lcp的长度。

暴力做法:直接暴力拓展
#include <bits/stdc++.h>
using namespace std;
int main() {
string s;
int d;
cin >> s >> d;
int len = s.size();
int pos = 0;
int ans = 0;
while (pos < len) {
int maxn = d;
for (int k = 1; k <= d; k++) {
int i = pos;
while (s[i] == s[i + k] && i < len)
i++;
maxn = max(maxn, i - pos + k);
// cout << maxn << endl;
}
//cout << maxn << " " << pos << endl;
ans++;
pos = pos + maxn;
}
cout << ans << endl;
}
SA:后缀数组的height数组可以直接得到两个串的lcp
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 5;
struct SA {
char s[N];
int sa[N], cnt[N], t1[N], t2[N], rk[N], height[N];
int n;
void build_sa() {
int m = 128; // 字符集大小,这里是ASCII码,如果是小写字母,就是26
int *x = t1, *y = t2;
n++;
for (int i = 0; i < m; i++)
cnt[i] = 0; // 初始化 cnt数组
// 进行第一轮计数排序
for (int i = 0; i < n; i++)
cnt[x[i] = s[i]]++;
for (int i = 1; i < m; i++)
cnt[i] += cnt[i - 1];
for (int i = n - 1; i >= 0; i--)
sa[--cnt[x[i]]] = i;
for (int k = 1; k <= n; k <<= 1) {
int p = 0;
// 进行对第二关键字基数排序
for (int i = n - k; i < n; i++)
y[p++] = i;
for (int i = 0; i < n; i++)
if (sa[i] >= k) y[p++] = sa[i] - k;
// 进行对第一关键字基数排序
for (int i = 0; i < m; i++)
cnt[i] = 0;
for (int i = 0; i < n; i++)
cnt[x[y[i]]]++;
for (int i = 1; i < m; i++)
cnt[i] += cnt[i - 1];
for (int i = n - 1; i >= 0; i--)
sa[--cnt[x[y[i]]]] = y[i];
swap(x, y); // 交换x和y
p = 1;
x[sa[0]] = 0;
for (int i = 1; i < n; i++)
x[sa[i]] = y[sa[i - 1]] == y[sa[i]] && y[sa[i - 1] + k] == y[sa[i] + k] ? p - 1 : p++;
if (p >= n) break;
m = p;
}
int k = 0;
n--;
for (int i = 0; i <= n; i++)
rk[sa[i]] = i;
for (int i = 0; i < n; i++) {
// if(rk[i] == 0) continue;
if (k) k--;
int j = sa[rk[i] - 1];
while (s[i + k] == s[j + k])
k++;
height[rk[i]] = k;
}
}
// void get_height() {
// }
int RMQ[N][20], Log[N];
void initRMQ(int n) {
for (int i = 2; i <= n; i++) {
Log[i] = Log[i >> 1] + 1;
}
for (int i = 1; i <= n; i++)
RMQ[i][0] = height[i];
for (int j = 1; (1 << j) <= n; j++)
for (int i = 1; i + (1 << j) - 1 <= n; i++)
RMQ[i][j] = min(RMQ[i][j - 1], RMQ[i + (1 << (j - 1))][j - 1]);
}
int query(int l, int r) {
int k = Log[r - l + 1];
return min(RMQ[l][k], RMQ[r - (1 << k) + 1][k]);
}
int lcp(int x, int y) {
x = rk[x], y = rk[y];
if (x > y)
swap(x, y);
return query(x + 1, y); // 返回sa[x]和sa[y]的最长公共前缀
}
} sa_;
int main() {
int d;
cin >> sa_.s >> d;
sa_.n = strlen(sa_.s);
sa_.build_sa();
sa_.initRMQ(sa_.n);
int ans = 0;
for (int i = 0; i < sa_.n;) {
int maxn = d; // 前串长度
for (int k = 1; k <= d && i + k < sa_.n; k++) { // 枚举前串长度
maxn = max(maxn, k + sa_.lcp(i, i + k)); // 更新最大长度
}
ans++;
i += maxn;
}
cout << ans << endl;
}
kmp:kmp可以直接得到最小循环节的大小(代码来自cf)
string t;
int d, n;
int kmp[N];
int calc(int start) {
kmp[start] = start;
for (int i = start + 1, j = start; i < n; i++) {
while (j > start && t[i] != t[j])j = kmp[j - 1];
j += (t[i] == t[j]);
kmp[i] = j;
if (i - kmp[i] >= d)
return i;
}
return n;
}
void doWork() {
cin >> t >> d;
n = t.size();
int ans = 0;
for (int i = 0; i < n; i = calc(i)) {
ans += 1;
}
cout << ans << '\n';
}
int32_t main() {
#ifdef ONLINE_JUDGE
ios_base::sync_with_stdio(0);
cin.tie(0);
#endif // ONLINE_JUDGE
int t = 1;
// cin >> t;
while (t--)
doWork();
return 0;
}