Lyndon 分解 & runs

wcz在2022年集训队论文《浅谈与Lyndon理论有关的字符串组合问题》中做过详细介绍,由于笔者太菜,这里只做简单介绍,并且不做证明。

Lyndon 分解

Lyndon串:对于字符串\(s\),如果\(s\)的字典序严格小于\(s\)的所有后缀的字典序,我们称\(s\)是Lyndon串。
Lyndon分解:串\(s\)的Lyndon分解记为\(s = w^1w^2 \cdots w^k\),其中所有\(w^i\)为简单串,并且他们的字典序按照非严格单减排序,即$w^1 \geq w^2 \geq \cdots \geq w^k $。可以发现,这样的分解存在且唯一。

Duval 算法

显然我们能用\(SA\)\(O(nlogn)\)复杂度求出来,这里介绍一种线性方法\(Duval\)算法。

我们发现Lyndon串一定形如$ww \cdots \overline w \(,其中\)\overline w \(是\)w$的前缀。

我们维护三个指针\(i,j,k\),其中\(i\)表示当前处理的串的起始位置,\(j\)是上一个循环节的位置,\(k\)是新处理字符的位置。

有三种情况:

  • \(s[j] = s[k]\)时,依旧是循环出现的串,\(j,k\)后移。
  • \(s[j] < s[k]\)时,说明\(s[i,k]\)是一个新的Lyndon串,我们将其加入答案,并且将\(j\)更新为\(i\)\(i\)更新为\(k\)
  • \(s[j] > s[k]\)时,这就成为一个Lyndon串。\(i += j - k\)
void duval(char* s, int n) {
    for (int i = 1, j, k; i <= n;) {
        for (j = i, k = i + 1; k <= n && s[k] >= s[j]; ++k)
            if (s[k] > s[j])j = i;
            else ++j;
        while (i <= j) {
            i += k - j;
            gap.push_back(i - 1);
        }
    }
}

一些概念 & 性质

Lyndon Word

即Lyndon串

\(CFL(s)\)

即Lyndon分解

有效后缀

加上任意字符都是可能最小的后缀即位有效后缀

有效后缀一定形如$w_i^{c_i} \cdots w_k^{c_k} $

\(Significant /\ Suffixes /\ SS(w)\)

有效后缀族构成的集合

\(u,v \in SS(w)\) ,且 \(|u|<|v|\),则\(u\)\(v\)的border,且\(2|u|<|v|\)

考虑两个相邻的最小后缀,\(i,j\),若\(|j|<|i|<2|j|\),则\(i=AAB,j=AB\),其中\(B\)\(A\)的严格前缀。考虑\(k=B\),发现最小后缀一定在\(i,k\)中产生,所以\(2|u|<|v|\)

所以\(SS(w)\)的大小是\(log\)级别的。

放个例题:节日庆典

Lyndon Array

最大的\(j\)使得\(s[i,j-1]\)是Lyndon串。

最长的 Lyndon 子串是无交集的。

bool compare(int x, int y) {
    int len = LCP(x, y);
    return s[x + len] < s[y + len];
}
int ly[MAXN];
void getsuf(bool op = 0) {
    ly[n] = n + 1; lim[n] = 0;
    for (int i = n - 1; i >= 1; --i) {
        ly[i] = i + 1;
        while (ly[i] != n + 1 && compare(ly[i], i) == op)
            ly[i] = ly[ly[i]];
        lim[i] = lim[ly[i]] + 1;
    }
}

应用

  • 最小表示法
    \(s\)扩展成\(ss\),Lyndon分解后,起点小于\(n\)终点大于\(n\)的串就是最小表示法。

  • 寻找每一个前缀的最小后缀

runs

  • k次方串:\(w^k\)\(w\)串重复\(k\)次构成的字符串。

  • run:满足一个子串\(s[i,j]\)周期为\(p\),至少循环两次,且前后都不能扩展,那么我们称\(r = (i,j,p)\)构成\(S\)的一个run,记$e_r = \frac{j-i+1}{p} $为它的指数。

性质

  • 周期相同的两个\(runs\ r =(i_1,j_1,p)\)\(r_2=(i_2,j_2,p)\)的交长度小于\(p\)

  • 任何一个\(run\ r=(i,j,p)\)可以导出\(j-i+2-2p\)个本原平方串,即 \(S[i,j]\) 的所有长度为\(2p\)的子串;同时每个本原平方子串都按照上述规则由唯一的一个\(run\)导出。

  • 一个字符串的 \(runs\) 的个数在\(O(n)\)级别。

实现:
在不同字符大小定义下,找到Lyndon 数组,并通过\(LCP\),和\(LCS\)扩展获得。

下面是洛谷模板题代码,SA的部分在这里提到过。

struct Runs {
    char s[MAXN];
    int n;
    Sa sa, rsa;
    int ly[MAXN];
    void init(int _n) {
        n = _n;
        memcpy(sa.s, s, sizeof(s));
        reverse(s + 1, s + 1 + n);
        memcpy(rsa.s, s, sizeof(s));
        reverse(s + 1, s + 1 + n);
        sa.getSA(); sa.getHeight(); sa.initST();
        rsa.getSA(); rsa.getHeight(); rsa.initST();
    }
    int LCP(int x, int y) {
        return sa.LCP(x, y);
    }
    int LCS(int x, int y) {
        return rsa.LCP(n - x + 1, n - y + 1);
    }
    bool compare(int x, int y) {
        int len = LCP(x, y);
        return s[x + len] < s[y + len];
    }
    vector<array<int, 3>>ans;
    void run(int op) {
        for (int i = n - 1; i >= 1; --i) {
            ly[i] = i + 1;
            while (ly[i] && compare(ly[i], i) == op)
                ly[i] = ly[ly[i]];
        }
        for (int i = 1; i <= n; i++) {
            if (!ly[i])continue;
            int x = i, y = ly[i], p = y - x;
            int Lcs = LCS(x, y), Lcp = LCP(x, y);
            int l = x - Lcs + 1, r = y + Lcp - 1;
            if (Lcs + Lcp > p)
                ans.push_back({ l,r,p });
        }
    }
    void solve() {
        run(0); run(1);
        sort(all(ans));
        ans.erase(unique(all(ans)), ans.end());
        printf("%d\n", ans.size());
        for (auto [x, y, p] : ans)
            printf("%d %d %d\n", x, y, p);
    }
}run;

例题

luogu: P6656 【模板】Runs

luogu: P6114 【模板】Lyndon 分解

CF: 594 E. Cutting the Line

牛客:简单字符串

luogu: P5211 [ZJOI2017] 字符串

posted on 2024-07-24 14:12  Quixotica  阅读(40)  评论(0编辑  收藏  举报