Division + LCP (hard version)
题目描述
这是问题的困难版本。在这个版本中 l ≤ r l\le r l≤r 。
给你一个字符串 s s s 。对于固定的 k k k ,考虑将 s s s 恰好分成 k k k 个连续的子串 w 1 , … , w k w_1,\dots,w_k w1,…,wk 。设 f k f_k fk 是所有分割中最大可能的 L C P ( w 1 , … , w k ) LCP(w_1,\dots,w_k) LCP(w1,…,wk) 。
L C P ( w 1 , … , w m ) LCP(w_1,\dots,w_m) LCP(w1,…,wm) 是字符串 w 1 , … , w m w_1,\dots,w_m w1,…,wm 的最长公共前缀的长度。
例如,如果 s = a b a b a b c a b s=abababcab s=abababcab 和 k = 4 k=4 k=4 ,可能的除法是 a b a b a b c a b \color{red}{ab}\color{blue}{ab}\color{orange}{abc}\color{green}{ab} abababcab 。由于 a b ab ab 是这四个字符串的最长公共前缀,因此 L C P ( a b , a b , a b c , a b ) LCP(\color{red}{ab},\color{blue}{ab},\color{orange}{abc},\color{green}{ab}) LCP(ab,ab,abc,ab) 就是 2 2 2 。
您的任务是找到 f l , f l + 1 , … , f r f_l,f_{l+1},\dots,f_r fl,fl+1,…,fr 。
输入描述
第一行包含一个整数 t t t ( 1 ≤ t ≤ 1 0 4 1 \le t \le 10^4 1≤t≤104 ) - 测试用例数。
每个测试用例的第一行包含两个整数 n n n , l l l , r r r ( 1 ≤ l ≤ r ≤ n ≤ 2 ⋅ 1 0 5 1 \le l \le r \le n \le 2 \cdot 10^5 1≤l≤r≤n≤2⋅105 ) - 字符串长度和给定范围。
每个测试用例的第二行包含长度为 n n n 的字符串 s s s ,所有字符均为小写英文字母。
保证所有测试用例中 n n n 的总和不超过 2 ⋅ 1 0 5 2\cdot 10^5 2⋅105 。
输出描述
对于每个测试用例,输出 r − l + 1 r-l+1 r−l+1 个值即 f l , … , f r f_l,\dots,f_r fl,…,fr 。
样例输入
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
样例输出
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
原题
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAX = 2e5 + 6;
int ans[MAX];
vector<int> z_function(string s) // z函数
{
int n = (int)s.length();
vector<int> z(n);
for (int i = 1, l = 0, r = 0; i < n; ++i)
{
if (i <= r && z[i - l] < r - i + 1)
{
z[i] = z[i - l];
}
else
{
z[i] = max(0, r - i + 1);
while (i + z[i] < n && s[z[i]] == s[i + z[i]])
++z[i];
}
if (i + z[i] - 1 > r)
l = i, r = i + z[i] - 1;
}
return z;
}
int f(vector<int> &z, int len) // 计算len长度前缀在字符串中的数量
{
int n = z.size();
int cnt = 1;
for (int i = len; i < n;)
{
if (z[i] >= len)
cnt++, i += len;
else
i++;
}
return cnt;
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t;
cin >> t;
while (t--)
{
int n, Left, Right;
cin >> n >> Left >> Right;
string s;
cin >> s;
vector<int> z = z_function(s);
// 分两种情况处理
// 先处理k>√n
// 对于k>√n,由于k*LCP<=n,所以LCP<=√n,于是就可以处理每个<=√n的LCP,找到对应可满足的最大的k
for (int len = ceil(sqrt(n)); len >= 1; len--)
{
int num = f(z, len);
ans[num] = max(ans[num], len);
}
// 由可满足的最大的k的LCP来更新较小的k的LCP
for (int i = n - 1; i >= 1; i--)
{
ans[i] = max(ans[i], ans[i + 1]);
}
// 再处理k≤√n
for (int k = 1; k <= ceil(sqrt(n)); k++)
{
int l = 0, r = n / k;
while (l < r)
{
int mid = (l + r + 1) / 2; // 这里要 l + r +1 要不然会死循环
if (f(z, mid) >= k)
{
l = mid; // mid这个位置 满足条件之后 查找 [mid , right]的位置, 所以l移到mid的位置
}
else
{
r = mid - 1; // [mid,r] 不满足条件, 所以要移到满足条件的一方, r = mid - 1
}
}
// 最后的l,r是答案
ans[k] = l;
}
// 输出答案
for (int i = Left; i <= Right; i++)
{
if (i == Right)
cout << ans[i] << '\n';
else
cout << ans[i] << ' ';
}
// 清空该测试用例的ans
for (int i = 1; i <= n; i++)
{
ans[i] = 0;
}
}
return 0;
}

文章讨论了一个编程问题,涉及计算给定字符串如何按照指定数量分割成子串,使得这些子串的最长公共前缀最大。使用了z函数和分治策略解决此问题,输出特定范围内LCP的最大值列表。

被折叠的 条评论
为什么被折叠?



