Lyndon 理论学习笔记

upd:修了一些不严谨的表述。

字符串,太深刻了 /kk

引理、结论的证明建议不要跳过,很多问题的思考过程和引理、结论的证明过程是相通的。

定义

下标从 1 开始。

\(+\) 是字符串拼接。

\(|s|\) 表示 \(s\) 的长度。

\(s_i\) 表示 \(s\) 的第 \(i\) 个字符。

\(s^k\) 表示 \(k\)\(s\) 拼接的结果。

字符串间的大小关系用字典序比较,空串小于任何串。

Lyndon 分解

Duval 算法

字符串 \(s\) 是 Lyndon 串当且仅当 \(s\) 小于其每个非空真后缀。

有的文章里的“简单串”就是 Lyndon 串。

若字符串 \(s=w_1+\dots+w_k\),且 \(w_1\dots w_k\) 都是 Lyndon 串,且 \(w\) 字典序单调不增(\(w_i\ge w_{i+1}\)),

\(w_1\dots w_k\)\(s\) 的 Lyndon 分解。

引理 1:\(s\) 字典序最小的非空后缀为 \(w_k\)

证明:任意 \(w_i\) 都小于其每个非空真后缀,所以从 \(w_i\) 起头一定比从 \(w_i\) 中间起头(也就是以 \(w_i\) 的某个非空真后缀起头)优,

\(w_k\) 小于等于 \(w_i(i\ne k)\),所以从 \(w_k\) 起头一定比从 \(w_i\) 起头优,则肯定从 \(w_k\) 起头,这个后缀就是 \(w_k\)

引理 2:Lyndon 分解唯一存在。

证明:由引理 1 可以唯一确定 \(w_k\),而 \(w_1\dots w_{k-1}\) 一定是 \(s\) 去掉 \(w_k\) 后的串的 Lyndon 分解,于是可以确定 \(w_{k-1}\)

以此类推,可以唯一确定 \(w_1\dots w_k\)

由引理 2 可以轻松得到一个 \(O(n^2)\) 的 Lyndon 分解算法,但是这显然不是我们想要的。

Duval 算法可以 \(O(n)\) 求出 Lyndon 分解。

定义字符串 \(s\) 是近似 Lyndon 串,当且仅当 \(s=w^k+w'\),其中 \(w\) 是任意 Lyndon 串,\(k\ge 1\)\(w'\)\(w\) 的前缀(可以空)。

划分 \(s=s_1+s_2+s_3\),其中 \(s_1\) 的 Lyndon 分解已经得出,\(s_2\) 是一个近似 Lyndon 串,\(s_3\) 无要求,

初始 \(s_1\) 为空,\(s_2\)\(s\) 的第一个字符(单字符肯定是近似 Lyndon 串),\(s_3\)\(s\) 去掉第一个字符后的串,

现在需要调整划分方案,使得 \(s_1=s\)\(s_2=s_3=\varnothing\)

\(s_2=w^k+w'\)\(x=w_{|w'|+1}\),尝试每次去掉 \(s_3\) 开头的字符 \(y\) 加到 \(s_2\) 末尾,考虑 \(x,y\) 的关系:

  • \(x=y\)\(w^k+w'+x\) 仍是近似 Lyndon 串,无需调整划分方案,更新一下 \(w'\) 即可。
  • \(x<y\)\(w^k+w'+y\) 是 Lyndon 串。

证明:设 \(t=w^k+w'+y\)。需要证明 \(t\) 字典序最小的非空后缀是 \(t\) 本身。
引理 1 已经证过从 \(w\) 起头一定比从 \(w\) 中间起头优,
\(x<y\)\(w'+y>w\),而从第 \(i(i\ne 1)\)\(w\) 起头比从第一个 \(w\) 起头先走到 \(w'+y\)
则从第一个 \(w\) 起头一定比从第 \(i\)\(w\) 起头优,则肯定从第一个 \(w\) 起头,这个后缀就是 \(t\) 本身。

Lyndon 串肯定也是近似 Lyndon 串,所以也无需调整划分方案,

\(w'+y\) 并不是 \(w\) 的前缀,所以需要更新 \(w=t\)\(w'=\varnothing\)

  • \(x>y\)\(w^k+w'+y\) 啥也不是,必须调整划分方案。把 \(k\)\(w\) 划分进 \(s_1\),令 \(s_2\)\(s_1\) 后第一个字符,\(s_3\) 为剩下的串即可。

\(s_3\) 为空时像 \(x>y\) 一样调整划分方案(把 \(k\)\(w\) 划分进 \(s_1\),令 \(s_2\)\(s_1\) 后第一个字符,\(s_3\) 为剩下的串),

以此法不断去掉 \(s_3\) 开头的字符加到 \(s_2\) 末尾,可以发现 \(s_1\) 的长度一直在增加,最终一定能达到 \(s_1=s\)

实现的时候一般用类似最小表示法的三指针,维护 \(s_2\) 开头 \(i\)\(s_3\) 开头 \(j\),以及 \(k=j-|w|\)\(s_k=x\))。

//洛谷 P6114 【模板】Lyndon 分解
#include <cstdio>
#include <cstring>
int n, z;
char a[10000050];
int main()
{
    scanf("%s", a + 1), n = strlen(a + 1);
    int i = 1, j, k;
    while (i <= n)
    {
        j = i + 1, k = i;
        while (j <= n)
        {
            if (a[j] == a[k])
                ++j, ++k;
            else if (a[j] > a[k])
                ++j, k = i;
            else
                break;
        }
        while (i <= k)
            z ^= i + j - k - 1, i += j - k; //[i, i + j - k - 1] 是分解出的一个 Lyndon 串
    }
    printf("%d", z);
    return 0;
}

所以这玩意有啥用?

最小表示法

给定字符串 \(s\),求与 \(s\) 循环同构的字典序最小的串。(假设你不会正常的最小表示法)

想一想,怎么做

\(t=s+s\),则需要找出 \(t\) 的长度为 \(n\) 的最小子串。

\(t\) Lyndon 分解,引理 1 证过从 \(w_i\) 中间起头不优,且越靠后的 \(w_i\) 越优,

所以最优起头位置为最后一个 \(w_i\),使得 \(w_i\) 左端点后有不少于 \(n\) 个字符。

//洛谷 P1368 【模板】最小表示法 
#include <cstdio>
int n, m, z, a[10000050];
int main()
{
    scanf("%d", &m), n = m << 1;
    for (int i = 1; i <= m; ++i)
        scanf("%d", a + i), a[m + i] = a[i];
    int i = 1, j, k;
    while (i <= n)
    {
        j = i + 1, k = i;
        while (j <= n)
        {
            if (a[j] == a[k])
                ++j, ++k;
            else if (a[j] > a[k])
                ++j, k = i;
            else
                break;
        }
        while (i <= k)
        {
            if (i <= m + 1)
                z = i;
            i += j - k;
        }
    }
    for (int i = z; i < z + m; ++i)
        printf("%d ", a[i]);
    return 0;
}

k 划分见过没

给定字符串 \(s\) 和整数 \(k\),把 \(s\) 划分为不超过 \(k\) 个串 \(c_1\dots c_m(m\le k)\),求 \(\max c_i\) 的最小值。

想一想,怎么做

\(s\) Lyndon 分解得到 \(s=w_1^{m_1}+\dots+w_z^{m_z}\),则 \(\max c_i\) 有多大取决于其分到多少个 \(w_1\)

根据鸽巢原理,\(\max c_i\) 至少分到 \(\left\lceil\dfrac {m_1}k\right\rceil\)\(w_1\)

\(k\mid m_1\),则每个 \(c_i\) 都分到 \(m_1/k\)\(w_1\),最后一个 \(c_i\) 还要承包 \(w_2^{m_2}+\dots+w_z^{m_z}\)

于是 \(\max c_i\) 就是最后一个 \(c_i\),即 \(w_1^{m_1/k}+w_2^{m_2}+\dots+w_z^{m_z}\)

否则有些 \(c_i\) 分到了 \(\left\lceil\dfrac {m_1}k\right\rceil\)\(w_1\)(A 类),有些 \(c_i\) 分到了 \(\left\lfloor\dfrac {m_1}k\right\rfloor\)\(w_1\)(B 类),

令最后一个 \(c_i\) 属于 B 类,这样其承包 \(w_2^{m_2}+\dots+w_z^{m_z}\)\(\max c_i\) 还是之前的一个 A 类,即 \(w_1^{\left\lceil\frac {m_1}k\right\rceil}\)

k 划分见过没 2(对每个后缀 Lyndon 分解)

\(f(s,k)\) 表示对 \(s,k\) 做《k 划分见过没》的结果,

给定字符串 \(s\),有 \(q\) 次询问,每次给定 \(x,y\),设 \(t\)\(s\) 长度为 \(x\) 的后缀,求 \(f(t,y)\)

想一想,怎么做

如果能对所有后缀 Lyndon 分解,那这题就做完了。

实际上只需要对每个后缀求出其分解出的 \(w_1\),因为 \(w_2\dots w_k\) 一定是这个后缀去掉 \(w_1\) 后的串的 Lyndon 分解。

结论:对于 \(s\)\(i\) 开头的后缀 \(S_i\)\(S_{n+1}=\varnothing\))求出最小的 \(j\) 使得 \(j>i,S_j<S_i\),则 \(S_i\) 分解出的 \(w_1\)\(s\)\(i\)\(j-1\) 的子串。

证明:只需要证 \(w_1\) 是 Lyndon 串,且 \(w_1\ge w_2\),剩下的归纳即可。

  • \(w_1\) 是 Lyndon 串:考虑反证法,设 \(w_1\) 有非空后缀 \(t\) 小于 \(w_1\),而 \(S_i\)\(t\) 起头的后缀 \(X\) 又大于 \(S_i=Y\),所以 \(t\) 肯定是 \(w_1\) 的真前缀。对 \(X\)\(Y\) 同时去掉前缀 \(t\),得到 \(S_j=X'\) 大于 \(S_i\)\(w_1\) 的某个非空后缀 \(t'\) 起头的后缀 \(Y'\),则 \(Y'<X'<S_i\),这样 \(w_2\) 就应该以 \(t'\) 开头。但 \(w_2\) 并没有以 \(t'\) 开头,所以 \(w_1\) 是 Lyndon 串。

  • \(w_1\ge w_2\):考虑反证法,设 \(w_1<w_2\)。而 \(S_i>S_j\),所以 \(w_1\) 肯定是 \(w_2\) 的真前缀。对 \(S_i\)\(S_j\) 同时去掉 \(w_1\) 前缀,得到 \(S_j\) 大于 \(S_i\)\(w_2\) 的某个非空后缀 \(t\) 起头的后缀,这样 \(w_3\) 就应该以 \(t\) 开头。但 \(w_3\) 并没有以 \(t\) 开头,所以 \(w_1\ge w_2\)

求出 SA 后扫描线 + set 即可对每个 \(S_i\) 求出最小的 \(j\) 使得 \(j>i,S_j<S_i\),然后就做完了。

HDU 6761 Minimum Index(对每个前缀 Lyndon 分解)

给定字符串 \(s\),求 \(s\) 每个前缀的最小非空后缀。

想一想,怎么做

考虑在 Duval 算法的过程中求出 \(s_1+s_2\) 每个时刻的最小非空后缀。

\(s_2=w\),则最小非空后缀为 \(w\)

\(s_2=w^k+w'\),此时引理 1 的分析不再完全适用:从 \(w\) 中间起头仍然不如从 \(w\) 起头,

但是从 \(w'\) 中间起头可能优于从 \(w'\) 起头,因为 \(w'\) 不是 Lyndon 串。

所以只有可能从 \(w\) 起头或从 \(w'\) 中间起头,可以发现去掉后 \(|w|\) 个字符后答案不变,而去掉后 \(|w|\) 个字符后的答案早已求出。

可以发现过程中 \(s_1+s_2\) 会覆盖每个前缀,所以这就够了。

实际上本题对每个前缀求出了其 Lyndon 分解出的最后一个串 \(w_k\),可以由此得到每个前缀的 Lyndon 分解。

//HDU 6761 Minimum Index
#include <cstdio>
#include <cstring>
#define M 1000000007
int T, n, z[10000050];
char a[10000050];
int main()
{
    scanf("%d", &T);
    while (T--)
    {
        scanf("%s", a + 1), n = strlen(a + 1);
        int i = 1, j, k;
        while (i <= n)
        {
            j = i + 1, k = i;
            while (1)
            {
                if (k == i)
                    z[j - 1] = i;
                else
                    z[j - 1] = z[k - 1] + j - k;
                if (j > n)
                    break;
                if (a[j] == a[k])
                    ++j, ++k;
                else if (a[j] > a[k])
                    ++j, k = i;
                else
                    break;
            }
            while (i <= k)
                i += j - k;
        }
        int Z = 0;
        for (int i = 1, o = 1; i <= n; ++i)
            Z = (Z + 1ll * z[i] * o) % M, o = o * 1112ll % M;
        printf("%d\n", Z);
    }
    return 0;
}

洛谷 P5334 [JSOI2019] 节日庆典

给定字符串 \(s\),求 \(s\) 每个前缀的最小表示法。

想一想,怎么做

求某个串 \(s\) 的最小表示,实际上就是求 \(s\) 的一个非空后缀 \(S\),设 \(s\) 去掉 \(S\) 后得到的前缀为 \(P\),求 \(\min(S+P)\)

直接取 \(s\) 的最小非空后缀作为 \(S\) 肯定不行,比如 \(s=\texttt{baa}\) 时,\(s\) 的最小非空后缀为 \(\texttt{a}\),取 \(S=\texttt{a}\) 得到 \(S+P=\texttt{aba}\)

但是取 \(S=\texttt{aa}\) 可以得到 \(S+P=\texttt{aab}\),比直接取最小非空后缀作为 \(S\) 要优。

然而,我们仍然可以沿用上一题求每个前缀的最小非空后缀的思路,在 Lyndon 分解的过程中求解。

考虑在 Lyndon 分解的过程中求出 \(s_1+s_2\) 每个时刻的最小表示法。

\(s_2=w\),取 \(S=w\)

\(s_2=w^k+w'\),此时去掉后 \(|w|\) 个字符后最优 \(S\) 可能会变,

因为可能取 \(S=w^k+w'\) 最优,但是去掉后 \(|w|\) 个字符后就取不到 \(S=w^k+w'\) 了,

所以需要考虑去掉后 \(|w|\) 个字符后的最优 \(S\)(下文设为 \(T\))和 \(w^k+w'\)

如果 \(|T|>|w^{k-1}+w'|\),那么此时的最优 \(S\) 根本取不到 \(T\),直接取 \(w^k+w'\) 即可,

否则 \(T\) 要么从某个 \(w\) 起头,要么从 \(w'\) 的某个后缀起头,

结论:\(|T|\le|w^{k-1}+w'|\) 时,\(T\) 一定是 \(w^k+w'\) 的前缀。

证明:若 \(T\) 从某个 \(w\) 起头,则 \(T\) 形如 \(w^p+w'(p<k)\),一定是 \(w^k+w'\) 的前缀。

\(T\)\(w'\) 的某个后缀 \(t\),则 \(t\) 一定是 \(w\) 的前缀,

否则 \(t\)\(w\) 的比较在 \(|t|\) 前就结束了,若 \(t<w\)\(w\) 不是 Lyndon 串,若 \(t>w\)\(T\) 不应该从 \(t\) 起头,而应该从 \(w\) 起头。

所以 \(w^k+w'\)\(T\) 的关系形如下图:

现在需要比较从 \(\uparrow_A\)\(\uparrow_B\) 开头的两种答案,

它们的前 \(|T|\) 个字符相同,所以只需要比较从 \(\downarrow_A\)\(\downarrow_B\) 开头的两种答案,

此时只需要求原串与后缀的 LCP,预处理 Z 函数即可。

//洛谷 P5334 [JSOI2019] 节日庆典 
#include <cstdio>
#include <cstring>
#include <algorithm>
#define M 1000000007
using namespace std;
int n, z[10000050], Z[10000050];
char a[10000050];
int main()
{
    scanf("%s", a + 1), n = strlen(a + 1);
    z[1] = n;
    for (int i = 2, l = 0, r = 0; i <= n; ++i)
    {
        if (i <= r)
            z[i] = min(z[i - l + 1], r - i + 1);
        while (i + z[i] <= n && a[i + z[i]] == a[z[i] + 1])
            ++z[i];
        if (i + z[i] - 1 > r)
            l = i, r = i + z[i] - 1;
    }
    int i = 1, j, k;
    while (i <= n)
    {
        j = i + 1, k = i;
        while (1)
        {
            if (!Z[j - 1]) // 这题在最小表示法相同时要输出最小开头,所以取每个前缀第一次被 s1+s2 覆盖时的答案
            {
                if (k == i || k - Z[k - 1] > k - i) // s2=w 时直接取 w,|T|>|w^(k-1)+w'| 时直接取 w^k+w'
                    Z[j - 1] = i;
                else //|T|<=|w^(k-1)+w'|,用 Z 函数比较两种答案即可
                {
                    bool F;
                    int S = i, T = Z[k - 1] + j - k;
                    int A = S + j - T, B = 1;
                    if (z[A] >= j - A)
                    {
                        A = 1, B += j - A;
                        if (z[B] >= T - B)
                            F = S < T;
                        else
                            F = a[z[B] + 1] < a[B + z[B]];
                    }
                    else
                        F = a[A + z[A]] < a[z[A] + 1];
                    if (F)
                        Z[j - 1] = S;
                    else
                        Z[j - 1] = T;
                }
            }
            if (j > n)
                break;
            if (a[j] == a[k])
                ++j, ++k;
            else if (a[j] > a[k])
                ++j, k = i;
            else
                break;
        }
        while (i <= k)
            i += j - k;
    }
    for (int i = 1; i <= n; ++i)
        printf("%d ", Z[i]);
    return 0;
}

最小后缀族

考虑这样一个问题:给定字符串 \(s\),每次询问一个字符串 \(T\),求 \(s\) 的一个后缀 \(S\)(可空)使得 \(S+T\) 最小。

称可能成为最优 \(S\) 的后缀为 \(s\) 的有效后缀,\(s\) 的最小后缀族 \(SS(s)\) 就是 \(s\) 的所有有效后缀组成的集合。

引理 3:若 \(S\)\(T\) 都是 \(s\) 的有效后缀且 \(|S|<|T|\),则 \(S\)\(T\) 的 border 且 \(2|S|\le|T|\)

证明:首先一定有 \(S<T\),否则 \(S\)\(T\) 的比较在 \(|S|\) 前就结束了,\(S\) 不可能是有效后缀,

而且 \(S\) 一定是 \(T\) 的前缀,否则 \(S\)\(T\) 的比较在 \(|S|\) 前就结束了,\(T\) 不可能是有效后缀,

\(S\) 明显是 \(T\) 的后缀,所以 \(S\)\(T\) 的 border。

考虑反证法,若 \(2|S|>|T|\),设 \(S\) 长度为 \(|T|-|S|\) 的前缀为 \(u\),则 \(T\) 一定可以写成 \(u^2+v\),而 \(S=u+v\)

若存在字符串 \(y\) 使得 \(u+v+y<v+y\),则 \(u^2+v+y<u+v+y\)\(S=u+v\) 要么比 \(T=u^2+v\) 劣,要么比 \(v\) 劣,\(S\) 一定不是有效后缀,

但是 \(S\) 确实是有效后缀,所以 \(2|S|\le|T|\)

由引理 3 可知,\(|SS(s)|=O(\log |S|)\)

引理 4:有效后缀一定形如 \(S_i=w_i^{c_i}+\dots+w_k^{c_k}\)\(S_{k+1}=\varnothing\))。

证明:引理 1 已经证过从 \(w_i\) 中间起头不优,只需要证一定从第一个 \(w_i\) 起头,

类似引理 3 后半部分的证法,\(w_i^x+\dots+w_k^{c_k}(x\ne c_i)\) 要么比 \(w_i^{x+1}+\dots+w_k^{c_k}\) 劣,要么比 \(w_i^{x-1}+\dots+w_k^{c_k}\) 劣,

则不从第一个 \(w_i\) 起头肯定不优,所以一定从第一个 \(w_i\) 起头。

\(SS(s)\) 咋求呢?\(\newcommand{\L}{\lambda}\)

考虑 Duval 算法过程中第一次 \(s_3=\varnothing\) 的时刻,设此时 \(s_2=w_{\L}^{c_{\L}}+w_{\L}'\)

可以发现 \(S_{\L}=w_{\L}^{c_{\L}}+w_{\L}'\)\(S_{\L+1}=w_{\L}'\),所以 \(S_{\L+1}\)\(S_{\L}\) 的前缀,

考虑 Duval 算法的过程,从 \(|s_2|=1\) 开始,每次往 \(s_2\) 后加一个字符后 \(s_2\) 一直是近似 Lyndon 串,

所以 \(s_2\) 的每个前缀都是近似 Lyndon 串,\(S_{\L+1}=w_{\L}'\) 明显是 \(s_2\) 的前缀,于是 \(S_{\L+1}\) 也是近似 Lyndon 串,

下次 \(s_3=\varnothing\)\(s_2=S_{\L+1}=w_{\L+1}^{c_{\L+1}}+w_{\L+1}'\)\(S_{\L+2}=w_{\L+1}'\),所以 \(S_{\L+2}\)\(S_{\L+1}\) 的前缀,

以此类推,\(\forall i\ge\L\)\(S_{i+1}\)\(S_i\) 的前缀,并且每次 \(s_3=\varnothing\) 时的 \(s_2\) 取遍 \(S_i(i\ge\L)\)

\(\forall i<\L,S_i=w_i^{c_i}+w_i'+s_3,S_{i+1}=w_i'+s_3\),而 \(s_3<w_i\),所以 \(S_{i+1}\) 肯定不是 \(S_i\) 的前缀。于是:

引理 5:\(S_{i+1}\)\(S_i\) 的前缀当且仅当 \(i\ge\L\)

所以,结合引理 3 可以确定,\(\forall i<\L,S_i\notin SS(s)\)。实际上 \(\forall i\ge\L,S_i\in SS(s)\),接下来将说明这一点。

回到原问题:给定字符串 \(s\),每次询问一个字符串 \(T\),求 \(s\) 的一个后缀 \(S_i\)(可空)使得 \(S_i+T\) 最小。

首先 \(S_i\in SS(s)\),所以 \(i\ge\L\)。对于 \(i\ge\L\)\(w_i=S_{i+1}+y_i\)\(x_i=y_i+S_{i+1}\),则 \(S_i=S_{i+1}+x_i^{c_i}\)

(笑点解析:论文这里 typo 了,写成了 \(S_i=S_{i+1}+y_i\),模了半天没模明白)

引理 6:\(\forall i\in[\L,k-1],x_i^{\infty}>x_{i+1}^{\infty}\)

证明:首先 \(x_i^{\infty}>y_i\),只需证 \(y_i>x_{i+1}^{\infty}\)

两边同时在前面拼接 \(S_{i+1}\),左边 \(S_{i+1}+y_i=w_i\),右边

\[\begin{aligned} &S_{i+1}+x_{i+1}^{\infty}\\ =&S_{i+1}+(y_{i+1}+S_{i+2})^{\infty}\\ =&S_{i+2}+x_{i+1}^{c_{i+1}}+(y_{i+1}+S_{i+2})^{\infty}\\ =&S_{i+2}+(y_{i+1}+S_{i+2})^{c_{i+1}}+(y_{i+1}+S_{i+2})^{\infty}\\ =&S_{i+2}+(y_{i+1}+S_{i+2})^{\infty}\\ =&(S_{i+2}+y_{i+1})^{\infty}+S_{i+2}\\ =&w_{i+1}^{\infty}+S_{i+2} \end{aligned} \]

字符串推式子见过没(

此时只需证 \(w_i>w_{i+1}^{\infty}\)

根据引理 5 的证明过程,\(S_{i+1}\)\(w_i\) 的前缀,那么 \(w_{i+1}\) 肯定也是 \(w_i\) 的前缀,

\(w_i=w_{i+1}^k+t\),其中 \(w_{i+1}\) 不是 \(t\) 的前缀,而 \(w_i\) 是 Lyndon 串,所以 \(t>w_i\)

而且 \(w_{i+1}\) 不是 \(t\) 的前缀,所以 \(t\)\(w_i\) 的比较在前 \(|w_{i+1}|\) 位就能结束,所以 \(t>w_{i+1}^{\infty}\)

两边同时在前面拼接 \(w_{i+1}^k\),得到 \(w_{i+1}^k+t>w_{i+1}^k+w_{i+1}^{\infty}\),所以 \(w_i>w_{i+1}^{\infty}\)

再次回到原问题:对给定的 \(T\),考虑比较 \(S_i+T\)\(S_{i+1}+T\)

首先 \(S_i+T=S_{i+1}+x_i^{c_i}+T\),只需比较 \(x_i^{c_i}+T\)\(T\)

(笑点解析:这里论文又 typo 了,翻转后缀见过没)

结论:\(x_i^{c_i}+T<T\) 当且仅当 \(x_i^{\infty}<T\)

证明:设 \(T=(x_i^{c_i})^k+t\),其中 \(x_i^{c_i}\) 不是 \(t\) 的前缀,\(k\) 可能等于 \(0\),则需要比较 \((x_i^{c_i})^{k+1}+t\)\((x_i^{c_i})^k+t\)

由于 \(x_i^{c_i}\) 不是 \(t\) 的前缀,前 \(|(x_i^{c_i})^{k+1}|\) 位一定能比出结果,所以用 \(x_i^{\infty}\)\(T=(x_i^{c_i})^k+t\) 一定能比出相同的结果。

所以 \(S_i+T<S_{i+1}+T\iff x_i^{c_i}+T<T\iff x_i^{\infty}<T\)

\(x_i^{\infty}\) 单调减,所以一定存在 \(p\) 使得 \(\forall i<p,x_i^{\infty}>T\iff S_i+T>S_{i+1}+T,\forall i\ge p,x_i^{\infty}<T\iff S_i+T<S_{i+1}+T\)

可以发现答案就是 \(S_p+T\),二分求出 \(p\) 即可。

注意到 \(\forall i\ge\L\)\(S_i\) 都有可能成为 \(S_p\),这样就证明了上面提出的 \(\forall i\ge\L,S_i\in SS(s)\)

下文将求出的 \(S_p\) 称为“后接 \(T\) 的最小后缀”。

所以这玩意有啥用?

最大后缀

给定字符串 \(s\),求 \(s\) 每个前缀的最大后缀。

(笑点解析:论文原题是求 \(s\) 的最大后缀)

想一想,怎么做

先做一下论文原题:

首先反转字母表之后就是求最小后缀了吗?并不是,

比如后缀 \(S\) 是后缀 \(T\) 的前缀时应该取较长的 \(T\),但是反转字母表后求最小后缀会取到较短的 \(S\)

考虑修补这个做法,反转字母表后设一个极大字符 \(\texttt{#}\),然后求出后接 \(\texttt{#}\) 的最小后缀,这样就可以取到较长的 \(T\) 了,

\(\texttt{#}\) 肯定大于所有 \(x_i\),所以后接 \(\texttt{#}\) 的最小后缀就是 \(S_{\L}\)

(但是根本没必要这么做,直接在 \(s\) 后面接一个 \(\texttt{#}\),然后求最小后缀就行了……)

考虑求 \(s\) 每个前缀的最大后缀。

肯定不能把每个前缀后面都接上 \(\texttt{#}\) 做一次,所以考虑对每个前缀求出 \(S_{\L}\)

考虑之前几个题的做法,\(s_1+s_2\) 会覆盖所有前缀,所以某个前缀第一次被 \(s_1+s_2\) 覆盖时,\(s_2\) 就是这个前缀的 \(S_{\L}\)

洛谷 P5334 [JSOI2019] 节日庆典(求每个前缀的 SS)

再 放 送

给定字符串 \(s\),求 \(s\) 每个前缀的最小表示法。

想一想,怎么做

求某个串 \(s\) 的最小表示,实际上就是求 \(s\) 的一个非空后缀 \(S\),设 \(s\) 去掉 \(S\) 后得到的前缀为 \(P\),求 \(\min(S+P)\)

根据引理 5,对于有效后缀 \(S_i\) 和不是有效后缀的后缀 \(T\)(明显有 \(S_i<T\)),\(S_i\) 一定不是 \(T\) 的前缀,

所以 \(S_i\)\(T\) 的比较在 \(|S_i|\) 前就结束了,最优 \(S\) 一定不会取到 \(T\)

所以求最优 \(S\) 只需要考虑有效后缀,现在只需要对每个前缀求出所有有效后缀。

\(P_i\) 表示 \(s\)\(i\) 结尾的前缀,设 \(SS(P_i)\) 表示 \(P_i\) 的所有有效后缀的起头位置集合,

根据有效后缀的定义,可以发现 \(SS(P_i)-SS(P_{i-1})\) 要么为 \(\{i\}\),要么为 \(\varnothing\)

于是考虑从 \(SS(P_{i-1})\) 递推到 \(SS(P_i)\)

考虑从短到长依次加入 \(SS(P_{i-1})\) 中每个起头位置对应的后缀 \(S_j+s_i\)

加入 \(S_j+s_i\) 时,设加入的上一个后缀为 \(S_p+s_i\),首先 \(S_p\) 肯定是 \(S_j\) 的前缀,设 \(S_j\) 的第 \(|S_p|+1\) 个字符为 \(x\)

\(x<s_i\),则前 \(|S_p+s_i|\) 位就能比出 \(S_p+s_i<S_j+s_i\),那肯定就不用加入 \(S_j+s_i\) 了,

\(x=s_i\)\(2|S_p+s_i|\le|S_j+s_i|\),则 \(S_p+s_i\) 有可能是有效后缀(满足引理 3 不一定是有效后缀),

不管 \(S_p+s_i\) 是不是有效后缀,保留 \(S_p+s_i\) 对复杂度和正确性都没影响,直接加入 \(S_j+s_i\) 即可,

否则 \(S_p+s_i\) 不可能是有效后缀(不满足引理 3 一定不是有效后缀),删除 \(S_p+s_i\) 后加入 \(S_j+s_i\) 即可。

再之前加入的后缀同样满足引理 3,所以它们也有可能是有效后缀,保留它们对复杂度和正确性都没影响,所以不用管它们。

上面的“有可能”使得维护出的 \(SS'(P_i)\) 只是满足引理 3 的一个后缀集合,而不是真正的有效后缀集合 \(SS(P_i)\)

\(SS(P_i)\) 肯定是 \(SS'(P_i)\) 的子集,而且 \(SS'(P_i)\) 的大小也是 \(O(\log|s|)\),这就够了。

\(SS'(P_i)\) 中的后缀满足引理 3,所以仍然可以用 Z 函数比较两个后缀对应的答案。总复杂度 \(O(|s|\log|s|)\)

甚至不需要 Lyndon 分解……

#include <cstdio>
#include <cstring>
#include <vector>
using namespace std;
int n, z[3000050];
char a[3000050];
vector<int> S[3000050];
int main()
{
    scanf("%s", a + 1), n = strlen(a + 1);
    z[1] = n;
    for (int i = 2, l = 0, r = 0; i <= n; ++i)
    {
        if (i <= r)
            z[i] = min(z[i - l + 1], r - i + 1);
        while (i + z[i] <= n && a[i + z[i]] == a[z[i] + 1])
            ++z[i];
        if (i + z[i] - 1 > r)
            l = i, r = i + z[i] - 1;
    }
    S[1].push_back(1), printf("1 ");
    for (int i = 2; i <= n; ++i)
    {
        S[i].push_back(i);
        for (auto j : S[i - 1])
        {
            if (a[i] < a[j + i - S[i].back()])
                continue;
            if (a[i] > a[j + i - S[i].back()] || i - j + 1 < i - S[i].back() + 1 << 1)
                S[i].pop_back(); //S_p+s_i 不可能是有效后缀,删除 S_p+s_i
            S[i].push_back(j);
        }
        int Z = S[i][0];
        for (int j = 1; j < S[i].size(); ++j)
        {
            int T = S[i][j];
            int A = T + i - Z + 1, B = 1;
            if (z[A] >= i - A + 1)
            {
                A = 1, B += i - A + 1;
                if (z[B] >= Z - B || a[z[B] + 1] < a[B + z[B]])
                    Z = T;
            }
            else if (a[A + z[A]] < a[z[A] + 1])
                Z = T;
        }
        printf("%d ", Z);
    }
    return 0;
}

洛谷 P5210 [ZJOI2017] 线段树(求区间 SS)

但是我不小心狂暴吸入了过多例题的题解,等我忘了之后重新思考一遍再写吧,,,

Lyndon 数组与 Runs

这是一级标题哦!跟上面的 Lyndon 分解就没什么关系了

我去咋这么多引理……等我看会

posted @ 2024-10-30 17:04  5k_sync_closer  阅读(87)  评论(3编辑  收藏  举报