G2. Division + LCP (hard version)

G2. Division + LCP (hard version)

This is the hard version of the problem. In this version $l\le r$.

You are given a string $s$. For a fixed $k$, consider a division of $s$ into exactly $k$ continuous substrings $w_1,\dots,w_k$. Let $f_k$ be the maximal possible $LCP(w_1,\dots,w_k)$ among all divisions.

$LCP(w_1,\dots,w_m)$ is the length of the Longest Common Prefix of the strings $w_1,\dots,w_m$.

For example, if $s=abababcab$ and $k=4$, a possible division is $\color{red}{ab}\color{blue}{ab}\color{orange}{abc}\color{green}{ab}$. The $LCP(\color{red}{ab},\color{blue}{ab},\color{orange}{abc},\color{green}{ab})$ is $2$, since $ab$ 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 $f_l,f_{l+1},\dots,f_r$.

Input

The first line contains a single integer $t$ ($1 \le t \le 10^4$) — the number of test cases.

The first line of each test case contains two integers $n$, $l$, $r$ ($1 \le l \le r \le n \le 2 \cdot 10^5$) — the length of the string and the given range.

The second line of each test case contains string $s$ of length $n$, all characters are lowercase English letters.

It is guaranteed that the sum of $n$ over all test cases does not exceed $2\cdot 10^5$.

Output

For each test case, output $r-l+1$ values: $f_l,\dots,f_r$.

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 简单()

  如果对每个 $k$ 都用 Division + LCP (easy version) 的方法二分出答案,时间复杂度是 $O(n^2 \log{n})$。可以反过来考虑,如果每个子串的 LCP 都是 $k$,最多可以划分成几个子串?记为 $f_k$。

  当求出 $f_k \, (1 \leq k \leq n)$ 后,对于每个 $i \, (l \leq i \leq r)$ 答案就是满足 $f_k \geq i$ 的最大的 $k$(显然可以通过减少子串的数量使得 LCP 仍是 $k$)。随着 $i$ 的增大 $k$ 必然是逐渐变小的(这是因为满足 $f_k \geq i$ 的 $k$ 的数量只会减少而不增加)。为此满足单调性,可以用双指针求出每个 $i$ 的答案。

  现在的问题是如何求出 $f_k$。对于固定的 $k$,显然 $s_0 \sim s_{k-1}$ 就是每个子串的 LCP,贪心的思路也很明显,如果有 $s_i \sim s_{i+k-1} = s_0 \sim s_{k-1}$,接下来应该从 $i+k$ 开始继续找下一个与 $s_0 \sim s_{k-1}$ 匹配且最近的子串。因为需要与前缀匹配,可以用 Z Algorithm,若 $z_i \geq k$ 则说明从 $i$ 开始长度为 $k$ 的子串与 $s_0 \sim s_{k-1}$ 匹配。

  如果对于每个 $k$ 都用上述做法求出 $f_k$ 时间复杂度是 $O(n^2)$,要优化的地方应该是找到下一个与前缀匹配的位置,即从某个位置 $i$ 开始满足 $z_j \geq k \, (i \leq j < n)$ 的最小的位置 $j$。我们可以先把所有满足 $z_i \geq k \, (0 \leq i < n)$ 的下标存储到 std::set,这样就可以通过二分求得下一个位置了。为此我们可以倒叙枚举 $k$,从而维护出满足 $z_i \geq k$ 的下标。

  当 LCP 等于 $k$ 时,最多可以划分出 $\frac{n}{k}$ 个子串,因此最多需要二分的次数就是 $\sum\limits_{k=1}^{n}{\frac{n}{k}} \approx n \log{n}$。

  AC 代码如下,时间复杂度为 $O(n \log^2{n})$:

#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;
}

  官方题解还给了根号分治的做法。主要是当划分的子串数量为 $s$ 时,LCP 为最大不超过 $k = \frac{n}{s}$,所以可以根据 $s$ 的大小选择不同的做法。

  当 $s \leq \sqrt{n}$ 时,可以直接二分答案,记作 $\text{ans}_i$ 表示划分成 $i$ 个子串时的 LCP。这部分的时间复杂度为 $O(n \sqrt{n} \log{n})$。

  而当 $s > \sqrt{n}$ 时,$k = \frac{n}{s} < \sqrt{n}$。直接暴力枚举 $k \, (1 \leq k \leq \sqrt{n})$,然后找到下一个满足 $z_i \geq k$ 的位置也直接暴力枚举。这部分的时间复杂度为 $O(n \sqrt{n})$。考虑如何更新答案,假如当 LCP 等于 $k$ 时最多可以划分成 $c$ 个子串,按理来说对于每个 $1 \leq i \leq c$ 应该更新 $\text{ans}_i \gets \max\{ \text{ans}_i, k \}$。为了避免超时可以先更新 $\text{ans}_c$,最后枚举完所有的 $k$ 时,倒叙枚举来更新 $\text{ans}_i \gets \max\{ \text{ans}_i, \text{ans}_{i+1} \}$。

  AC 代码如下,时间复杂度为 $O(n \sqrt{n} \log{n})$:

#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

posted @ 2024-05-03 20:10  onlyblues  阅读(63)  评论(0编辑  收藏  举报
Web Analytics