首页 > 其他分享 >后缀数组

后缀数组

时间:2022-10-09 21:15:44浏览次数:90  
标签:后缀 text lms 数组 sa 排序 rk

后缀数组相关

后缀数组

基础的数组有两个:\(\text{sa}[]\) 和 \(\text{rk}[]\)。\(\text{sa}[i]\) 表示“第 \(i\) 小的后缀是第几个后缀”,\(\text{rk}[i]\) 表示“第 \(i\) 个后缀是第几小的”。这两个数组可以通过排序产生。

考虑双关键字倍增排序。初始化把所有长度为 \(1\) 的子串排个序,得到一个 \(\text{sa}[]\) 和 \(\text{rk}_ 1[]\)。然后用 \(\text{rk}_ 1[i]\) 和 \(\text{rk}_ 1[i+1]\) 作为当前 \(i\) 的关键字对长度为 \(2\) 的子串排序,得到一个 \(\text{sa}[]\) 和 \(\text{rk}_ 2[]\)。然后用 \(\text{rk}_ 2[i]\) 和 \(\text{rk}_ 2[i+2]\) 作为当前 \(i\) 的关键字对长度为 \(4\) 的子串排序,得到一个 \(\text{sa}[]\) 和 \(\text{rk}_ 4[]\)……。倍增到最后得到一个后缀数组 \(\text{sa}[]\) 和 \(\text{rk}_ n[]\)。

使用基数排序,每轮排序的复杂度是 \(O(n)\) 的。由于进行了 \(O(\log n)\) 次,因此复杂度是 \(O(n \log n)\) 的。

Submission loj的设计就很优美

code
const int N = 1e6 + 10;
char ch[N];
int n, sa[N], rk[N];
void getsa(char ch[], int sa[], int rk[]) {
    int m = 127;
    static int cnt[N], rk2[N], key[N], id[N];
    memset(cnt+1, 0, m << 2);
    rep(i,1,n) rk[i] = ch[i], cnt[rk[i]]++;
    rep(i,1,m) cnt[i] += cnt[i-1];
    pre(i,n,1) sa[cnt[rk[i]] --] = i;
    for (int w = 1; ; w <<= 1) {
        int p = 0;
        for (register int i = n; i > n - w; -- i) id[++p] = i;
        rep(i,1,n) if (sa[i] > w) id[++p] = sa[i] - w;
        memset(cnt + 1, 0, m << 2);
        rep(i,1,n) key[i] = rk[id[i]], cnt[key[i]]++;
        rep(i,1,m) cnt[i] += cnt[i-1];
        pre(i,n,1) sa[cnt[key[i]] --] = id[i];
        memcpy(rk2+1, rk+1, n << 2);
        p = 0;
        rep(i,1,n) rk[sa[i]] = (
                        rk2[sa[i]] == rk2[sa[i-1]] and 
                        rk2[sa[i] + w] == rk2[sa[i-1] + w]
                     ) ? p : ++p;
        if (p == n) { rep(i,1,n) sa[rk[i]] = i; break; }
        m = p;
    }
}

当然你可以 \(O(n)\) 排序。有两种方法:DC3 和 SA-IS。DC3 难写常数还大,所以基本上没人用。这里介绍 SA-IS。
IS 是诱导排序,因为 cache 很友好所以常数小。而且码量不大

下面称从 \(i\) 位置开始的后缀为 后缀 \(i\)

我们在字符串最后加上去一个字典序小于字符串内任意字符的新字符 \(\#\)。将后缀分为两类:S 类和 L 类。有:

  1. 新加入的字符为 S 类。
  2. 若从 \(i\) 位置开始的后缀字典序严格小于从 \(i+1\) 开始的后缀,则从 \(i\) 位置开始的后缀为 S 类,反之为 L 类。

后缀的类型可以倒着扫一遍得到。具体地,若当前字符与后一位字符不同,则该后缀的类型可得;若相同则该后缀的类型与后一个后缀相同。

若后缀 \(i\) 为 S 类,后缀 \(j\) 为 L 类且字符 \(i =\) 字符 \(j\),则后缀 \(i\) 的字典序大于后缀 \(j\)。

然后排序。我们定义 lms(Left-Most-S-suffix) 串为左边是一个 L 型串的 S 型串。显然有 lms 串的数量最多是 \(\frac n2\) 的。定义 lms 子串为两个 lms 串中间(包含两端)的串。SA-IS 通过把原串拆成 lms 子串的方式缩小求解范围。

我们递归求得下层的 \(\text{sa}[]\),现在需要求得本层的 \(\text{sa}[]\)。使用诱导排序。步骤:

  1. 使用两部分桶记录 S 串和 L 串的数量
  2. 倒序扫 lms,放进它的 sa 对应的 S 型桶里
  3. 正序扫 sa,如果 sa - 1 位置的类型是 L 型就放进对应的 L 型桶里
  4. 倒序扫 sa,如果 sa - 1 位置的类型是 S 型就放进对应的 S 型桶里

有结论说明 lms 子串进行一遍诱导排序后就有序了。
然后考虑如何对 lms 离散化。如果首位不同则编号,相同则暴力匹配。

  1. 确定每个后缀的类型,找出lms类型后缀。
  2. 第一遍诱导排序,对lms排序。
  3. 对lms离散化。
  4. 如果lms互不相同,直接计算sa,否则递归。
  5. 恢复lms的位置,诱导排序一遍计算sa。

好 SA-IS 我之后再填吧

height

\(height[i] = \text{lcp}(\text{sa}[i],\text{sa}[i-1])\)。

引理:\(height[\text{rk}[i]] \ge height[\text{rk}[i-1]]-1\)。
然后我们就可以顺着 \(\text{rk}\) 数组扫一遍得到答案。

void getheight() {
    for (int i = 1, k = 0; i <= n; i++) {
        if (rk[i] == 0) continue;
        if (k) k--;
        while (s[i + k] == s[sa[rk[i] - 1] + k]) k++;
        height[rk[i]] = k;
    }
}

定理:\(\text{lcp}(sa[i],sa[j]) = \min_{k=i}^j height[k]\)

所以可以在 height 数组上建一个st表得到lcp。

前面的区域,以后再

标签:后缀,text,lms,数组,sa,排序,rk
From: https://www.cnblogs.com/joke3579/p/suffix-array.html

相关文章

  • 牛客网高频算法题系列-BM18-二维数组中的查找
    牛客网高频算法题系列-BM18-二维数组中的查找题目描述在一个二维数组array中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺......
  • C++数组
    概述C++支持数组数据结构,它可以存储一个固定大小的相同类型元素的顺序集合。数组是用来存储一系列数据,但它往往被认为是一系列相同类型的变量。数组的声明并不是声明一个个......
  • 查找数组中元素
    1.数组71032241544要查找的数:262.伪代码SetpositiontoOSetfoundtofalseWHILE(position<6ANDfoundisfalse)IF(numbers[position]equalssearchitem)......
  • 查找数组中元素
    输入一个固定长度的数组,并输入一个要查找的数,给出能不能检索到的伪代码并测试代码:Setpositionto0SetfoundtofalseWHILE(position<6ANDfoundisfalse)I......
  • 查找数组中元素
    伪代码固定长度为6setpositionto0Write“entervalueforwhichtosearch”Readsearchitem=75Setfoundtotrueifsearchitemisthere......
  • 查找数组中的元素
    查找数组中的元素1.输入数组并输入六个元素,查找数组中的元素伪代码点击查看伪代码Integernumber[6]Setpositionto0SetfoundtoFALSEWHILE(position<6ANDf......
  • js中类数组对象转数组
    constobj={"0":"vv","1":12,"2":"male","3":123456,length:4}constarr=Array.from(obj);console.log(arr);//['vv',12,'male',123456]for(constitem......
  • 竞赛-6201. 找出前缀异或的原始数组
    参加竞赛的一道题中等难度给你一个长度为n的整数数组pref。找出并返回满足下述条件且长度为n的数组arr:pref[i]=arr[0]^arr[1]^...^arr[i].注意^表示......
  • js删除数组指定元素
    js删除数组指定元素constperson=[{id:0,name:'zhangsan'},{id:1,name:'wangwu'},{......
  • 显示文件名的后缀名(保姆级)
    1、随便打开一个文件夹  上方  工具->文件夹选项->(上方)查看->(找到) 隐藏已知文件类型的扩展名 把√去掉点确认就好了     ......