G2. Division + LCP (hard version)
G2. Division + LCP (hard version)
This is the hard version of the problem. In this version .
You are given a string . For a fixed , consider a division of into exactly continuous substrings . Let be the maximal possible among all divisions.
is the length of the Longest Common Prefix of the strings .
For example, if and , a possible division is . The is , since is the Longest Common Prefix of those four strings. Note that each substring consists of a continuous segment of characters and each character belongs to exactly one substring.
Your task is to find .
Input
The first line contains a single integer () — the number of test cases.
The first line of each test case contains two integers , , () — the length of the string and the given range.
The second line of each test case contains string of length , all characters are lowercase English letters.
It is guaranteed that the sum of over all test cases does not exceed .
Output
For each test case, output values: .
Example
input
7
3 1 3
aba
3 2 3
aaa
7 1 5
abacaba
9 1 6
abababcab
10 1 10
aaaaaaawac
9 1 9
abafababa
7 2 7
vvzvvvv
output
3 1 0
1 1
7 3 1 1 0
9 2 2 2 0 0
10 3 2 1 1 1 1 1 0 0
9 3 2 1 1 0 0 0 0
2 2 1 1 1 0
解题思路
比 E 简单()
如果对每个 都用 Division + LCP (easy version) 的方法二分出答案,时间复杂度是 。可以反过来考虑,如果每个子串的 LCP 都是 ,最多可以划分成几个子串?记为 。
当求出 后,对于每个 答案就是满足 的最大的 (显然可以通过减少子串的数量使得 LCP 仍是 )。随着 的增大 必然是逐渐变小的(这是因为满足 的 的数量只会减少而不增加)。为此满足单调性,可以用双指针求出每个 的答案。
现在的问题是如何求出 。对于固定的 ,显然 就是每个子串的 LCP,贪心的思路也很明显,如果有 ,接下来应该从 开始继续找下一个与 匹配且最近的子串。因为需要与前缀匹配,可以用 Z Algorithm,若 则说明从 开始长度为 的子串与 匹配。
如果对于每个 都用上述做法求出 时间复杂度是 ,要优化的地方应该是找到下一个与前缀匹配的位置,即从某个位置 开始满足 的最小的位置 。我们可以先把所有满足 的下标存储到 std::set
,这样就可以通过二分求得下一个位置了。为此我们可以倒叙枚举 ,从而维护出满足 的下标。
当 LCP 等于 时,最多可以划分出 个子串,因此最多需要二分的次数就是 。
AC 代码如下,时间复杂度为 :
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 2e5 + 5;
char s[N];
int z[N];
vector<int> p[N];
int f[N];
void solve() {
int n, l, r;
scanf("%d %d %d %s", &n, &l, &r, s);
memset(z, 0, n << 2);
z[0] = n;
for (int i = 1, j = 1; i < n; i++) {
if (i < j + z[j]) z[i] = min(z[i - j], j + z[j] - i);
while (i + z[i] < n && s[i + z[i]] == s[z[i]]) {
z[i]++;
}
if (i + z[i] > j + z[j]) j = i;
}
for (int i = 0; i <= n; i++) {
p[i].clear();
}
for (int i = 0; i < n; i++) {
p[z[i]].push_back(i);
}
set<int> st({n});
for (int i = n; i; i--) {
st.insert(p[i].begin(), p[i].end());
f[i] = 0;
int x = 0;
while (x < n) {
x = *st.lower_bound(x);
if (x < n) f[i]++;
x += i;
}
}
for (int i = l, j = n; i <= r; i++) {
while (j && f[j] < i) {
j--;
}
printf("%d ", j);
}
printf("\n");
}
int main() {
int t;
scanf("%d", &t);
while (t--) {
solve();
}
return 0;
}
官方题解还给了根号分治的做法。主要是当划分的子串数量为 时,LCP 为最大不超过 ,所以可以根据 的大小选择不同的做法。
当 时,可以直接二分答案,记作 表示划分成 个子串时的 LCP。这部分的时间复杂度为 。
而当 时,。直接暴力枚举 ,然后找到下一个满足 的位置也直接暴力枚举。这部分的时间复杂度为 。考虑如何更新答案,假如当 LCP 等于 时最多可以划分成 个子串,按理来说对于每个 应该更新 。为了避免超时可以先更新 ,最后枚举完所有的 时,倒叙枚举来更新 。
AC 代码如下,时间复杂度为 :
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 2e5 + 5, B = 450;
int n, l, r;
char s[N];
int z[N];
int ans[N];
int get(int len) {
int s = 0;
for (int i = 0; i < n; i++) {
if (z[i] >= len) {
s++;
i += len - 1;
}
}
return s;
}
void solve() {
scanf("%d %d %d %s", &n, &l, &r, s);
memset(z, 0, n << 2);
z[0] = n;
for (int i = 1, j = 1; i < n; i++) {
if (i < j + z[j]) z[i] = min(z[i - j], j + z[j] - i);
while (i + z[i] < n && s[i + z[i]] == s[z[i]]) {
z[i]++;
}
if (i + z[i] > j + z[j]) j = i;
}
memset(ans, 0, n + 2 << 2);
for (int i = 1; i <= B; i++) {
int l = 0, r = n / i;
while (l < r) {
int mid = l + r + 1 >> 1;
if (get(mid) >= i) l = mid;
else r = mid - 1;
}
ans[i] = l;
}
for (int i = 1; i <= B; i++) {
int c = get(i);
ans[c] = max(ans[c], i);
}
for (int i = n; i; i--) {
ans[i] = max(ans[i], ans[i + 1]);
}
for (int i = l; i <= r; i++) {
printf("%d ", ans[i]);
}
printf("\n");
}
int main() {
int t;
scanf("%d", &t);
while (t--) {
solve();
}
return 0;
}
参考资料
Codeforces Round 943 (Div. 3) A - G:https://zhuanlan.zhihu.com/p/695745337
Codeforces Round 943 (Div. 3) Editorial:https://codeforces.com/blog/entry/129096
本文来自博客园,作者:onlyblues,转载请注明原文链接:https://www.cnblogs.com/onlyblues/p/18171549
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效