题目链接:POJ 3693

我们定义一个字符串的重复数为最大的数字 $R$ 满足这个字符串可以被分割为 $R$ 个相同的连续子字符串。

给定一个长度为 $n$ 的字符串,你需要找到它的重复数最大的子串。如果有多个答案,输出字典序最小的子串。

数据范围:$1 \le n \le 10 ^ 5$。


Solution

枚举长度 $L$,然后求长度为 $L$ 的子串最多能连续出现多少次。这里只考虑至少出现 $2$ 次的情况。假设在原字符串中连续出现 $2$ 次,记这个字符串为 $T$,那么 $T$ 肯定包括了字符 $S_L, S_{2L}, S_{3L}, \cdots$ 中的某相邻的两个。所以只需要求 $\text{suffix}(i \cdot L), \text{suffix}((i + 1) \cdot L)$ 的最长公共前缀和 $\text{prefix}(i \cdot L - 1), \text{prefix}((i + 1) \cdot L - 1)$ 的最长公共后缀。后者可以将原串反转后求出 $\text{lcp}$。

如果 $\text{lcp}$ 与 $\text{lcs}$(姑且这么叫吧 QAQ)的长度和为 $K$,那么该子串出现了 $\left\lfloor\frac{K}{L}\right\rfloor + 1$ 次。

对于 $L \not \mid K$ 的情况,我们发现子字符串的左端点可以在一个长度为 $K \bmod L + 1$ 的区间内。我们只需要保证在重复次数最大的情况下,这段区间的内的 $rk(i)$ 的最小值最小,就能保证字典序最小了。

时间复杂度:$\mathcal O(n \log n)$,根据调和级数易证枚举复杂度正确。


Code

#include <cstdio>
#include <cstring>
#include <algorithm>

const int N = 1e5 + 5, logN = 17;

int n, lg[N], f[N][logN];
char s[N];

template <int S>
struct SuffixArray {
    static const int N = S << 1;
    int n, m, a[N], sa[N], rk[N], bin[N], tmp[N], height[N], f[N][logN];
    void clear() {
        memset(a, 0, sizeof(a));
        memset(sa, 0, sizeof(sa));
        memset(rk, 0, sizeof(rk));
        memset(height, 0, sizeof(height));
    }
    void radixSort() {
        for (int i = 1; i <= m; i++) bin[i] = 0;
        for (int i = 1; i <= n; i++) bin[rk[i]]++;
        for (int i = 1; i <= m; i++) bin[i] += bin[i - 1];
        for (int i = n; i >= 1; i--) sa[bin[rk[tmp[i]]]--] = tmp[i];
    }
    template <class Tp>
    void build(Tp *_a, int _n, int _m) {
        n = _n, m = _m;
        std::copy(_a + 1, _a + n + 1, a + 1);
        for (int i = 1; i <= n; i++) rk[i] = a[i], tmp[i] = i;
        radixSort();
        for (int l = 1, p = 0; p < n; l <<= 1, m = p) {
            p = 0;
            for (int i = n - l + 1; i <= n; i++) tmp[++p] = i;
            for (int i = 1; i <= n; i++) if (sa[i] > l) tmp[++p] = sa[i] - l;
            radixSort();
            std::swap(rk, tmp);
            p = rk[sa[1]] = 1;
            for (int i = 2; i <= n; i++) {
                rk[sa[i]] = (tmp[sa[i - 1]] == tmp[sa[i]] && tmp[sa[i - 1] + l] == tmp[sa[i] + l]) ? p : ++p;
            }
        }
        for (int i = 1, k = 0; i <= n; i++) {
            k -= (k > 0);
            int j = sa[rk[i] - 1];
            for (; a[i + k] == a[j + k]; k++);
            height[rk[i]] = k;
        }
    }
    void buildST() {
        for (int i = 1; i <= n; i++) f[i][0] = height[i];
        for (int j = 1; (1 << j) <= n; j++) {
            for (int i = 1; i + (1 << j) - 1 <= n; i++) {
                f[i][j] = std::min(f[i][j - 1], f[i + (1 << (j - 1))][j - 1]);
            }
        }
    }
    int lcp(int l, int r) {
        l = rk[l], r = rk[r];
        if (l > r) std::swap(l, r);
        int k = lg[r - (++l) + 1];
        return std::min(f[l][k], f[r - (1 << k) + 1][k]);
    }
};

SuffixArray<N> A, B;

void buildST(int *a, int n) {
    for (int i = 1; i <= n; i++) f[i][0] = a[i];
    for (int j = 1; (1 << j) <= n; j++) {
        for (int i = 1; i + (1 << j) - 1 <= n; i++) {
            f[i][j] = std::min(f[i][j - 1], f[i + (1 << (j - 1))][j - 1]);
        }
    }
}
int query(int l, int r) {
    int k = lg[r - l + 1];
    return std::min(f[l][k], f[r - (1 << k) + 1][k]);
}
int main() {
    for (int i = 2; i < N; i++) lg[i] = lg[i >> 1] + 1;
    int cs = 0;
    while (~scanf("%s", s + 1) && s[1] != '#') {
        n = strlen(s + 1);
        A.build(s, n, 255), A.buildST();
        std::reverse(s + 1, s + n + 1);
        B.build(s, n, 255), B.buildST();
        std::reverse(s + 1, s + n + 1);
        buildST(A.rk, n);
        int mx = 0, rk = n + 1, ansl = 0, ansr = 0;
        for (int l = 1; l <= n; l++) {
            for (int i = 1, j = l + 1; j <= n; i += l, j += l) {
                int R = A.lcp(i, j), L = B.lcp(n - i + 2, n - j + 2);
                int k = L + R, cnt = k / l + 1;
                if (cnt > mx) mx = cnt, rk = n + 1;
                if (cnt == mx) {
                    int mn = query(i - L, i - L + k % l);
                    if (mn < rk) rk = mn, ansl = A.sa[mn], ansr = ansl + mx * l - 1;
                }
            }
        }
        printf("Case %d: ", ++cs);
        for (int i = ansl; i <= ansr; i++) putchar(s[i]);
        puts("");
    }
    return 0;
}
最后修改:2019 年 06 月 28 日
如果觉得我的文章对你有用,请随意赞赏