后缀数组学习与练习(入门向)
Zbq
算法概述:
首先后缀数组SA是后缀数组是后缀树的一个非常精巧的替代品,它比后缀树容易编程实现,能够实现后缀树的很多功能而时间复杂度也不太逊色,并且,它比后缀树所占用的空间小很多。可以说,在信息学竞赛中后缀数组比后缀树要更为实用。
在这之上主要学习了倍增算法来求后缀数组, 下面列举几个在学习中发现的困惑点。
- tmp数组储存的是第二关键字从小到大排序后的编码位置而不是编码,这点使我好久才弄明白代码的意义。
- 基数排序的最后一步反向来,保证在满足第一关键字的情况下,第二关键字的有序。
- 每次求出sa数组反向带入rank时,要有一个类似离散化的操作,老是把起点2写成1
- 求height数组是排名第i和I – 1的lcp,所以要还原sa进行比较
常见模型:
1.可重叠的最长重复子串问题
方法:求所有height的最大值就是答案
2.求不可重叠的最长重复子串问题
方法:首先二分length,转换成判定性问题,之后我们将所有height大于等于的划分成若干个段段,这样在每个段段里,两两之间都是lcp大于等于k的,如果其中有两个元素的sa值绝对值大于等于k那么就符合条件
3.可重叠的k次最长重复子串
方法:类似于上一个,我们要让只要有一个划分出来的段段的长度大于等于k – 1
4.求重复出现的子串数目
方法:可以考虑,每一个height值就能贡献当前值的答案,但是这样算下来,会有的子串算重复,考虑当前的和排名靠前一个的,如果当前的height大于前height的值,那么就多加上了前一个的height值,减去即可。
5.不相同子串计数问题
方法:和博客上写的不一样,这个题目我们可以先求出所有子串的个数,之后减去所有的height值,原因是每个子串必然是某个后缀的前缀。令S的长度为N,、则后缀SA[i]可以贡献出N-SA[i]个前缀。但其中有Height[i]个与之前的是重复的,减去之后正好能够得到答案。
6.字典序第k小字符串问题
方法:同上一个,我们在对不相同子串进行计数的时候,子串的字典序值是递增的,这样算到分界点然后二分或者枚举应该都可以
7.连续重复子串问题
方法:首先枚举子串的长度k,之后如果符合题目要求的话,suffix(1)和suffix(k + 1)的lcp应当是k + 1,这个就用些操作随便处理了,可以用st表, 也可以考虑两个后缀的sa必然相邻。
8.多个字符串的相关问题
方法:在本来的基础上,将字符串头尾相连,中间添加一个极大的字符,就可以进行一些常规操作了.
由于笔者蒟蒻 暂时就整理这些了
后缀数组练习 都是些板子题
1. P3809 【模板】后缀排序 求sa数组。。只是打个板子
2. [JSOI2007]字符加密Cipher 拆环为链 求sa数组
3. JZOJ 1598. 文件修复 求重复出现的子串数目
4. spoj694 Distinct Substrings 统计本质不同的子串个数
5. poj1743:Musical Theme 差分,二分答案后缀数组
6. [USACO06DEC]牛奶模式Milk Patterns 后缀数组 单调队列
7. [AHOI2013]差异 后缀数组 单调栈
8. [HAOI2016]找相同字符 后缀数组 单调栈
题目和解析
题目背景
这是一道模板题。
题目描述
读入一个长度为 nn 的由大小写英文字母或数字组成的字符串,请把这个字符串的所有非空后缀按字典序从小到大排序,然后按顺序输出后缀的第一个字符在原串中的位置。位置编号为 11 到 nn 。
输入输出格式
输入格式:
一行一个长度为 nn 的仅包含大小写英文字母或数字的字符串。
输出格式:
一行,共n个整数,表示答案。
方法:裸的求sa啊 -。-。-。-
代码:
#include<cstdio> #include<algorithm> #include<cstring> #define M 1000010 using namespace std; char s[M]; int tex[M], num[M], rank[M], sa[M], tmp[M], n, m, q; void qsort() { for(int i = 0; i <= m; i++) tex[i] = 0; for(int i = 1; i <= n; i++) tex[rank[tmp[i]]]++; for(int i = 1; i <= m; i++) tex[i] += tex[i - 1]; for(int i = n; i >= 1; i--) sa[tex[rank[tmp[i]]]--] = tmp[i]; } bool check(int l, int r, int wei) { return tmp[l] == tmp[r] && tmp[l + wei] == tmp[r + wei]; } void suffix() { for(int i = 1; i <= n; i++) rank[i] = num[i], tmp[i] = i; m = 127; qsort(); for(int w = 1; q < n; w <<= 1, m = q) { q = 0; for(int i = n - w + 1; i <= n; i++) tmp[++q] = i; for(int i = 1; i <= n; i++) if(sa[i] > w) tmp[++q] = sa[i] - w; qsort(); swap(rank, tmp); q = rank[sa[1]] = 1; for(int i = 2; i <= n; i++) if(check(sa[i], sa[i - 1], w)) rank[sa[i]] = q; else rank[sa[i]] = ++q; } } int main() { scanf("%s", s); n = strlen(s); for(int i = 1; i <= n; i++) num[i] = s[i - 1]; suffix(); for(int i = 1; i <= n; i++) printf("%d ", sa[i]); return 0; }
***********************************************************************************
2. [JSOI2007]字符加密Cipher
Time Limit: 10 Sec Memory Limit: 162 MB
Submit: 4175 Solved: 1694
[Submit][Status][Discuss]
Description
喜欢钻研问题的JS 同学,最近又迷上了对加密方法的思考。一天,他突然想出了一种他认为是终极的加密办法:把需要加密的信息排成一圈,显然,它们有很多种不同的读法。例如下图,可以读作:
JSOI07 SOI07J OI07JS I07JSO 07JSOI 7JSOI0 把它们按照字符串的大小排序: 07JSOI 7JSOI0 I07JSO JSOI07 OI07JS SOI07J 读出最后一列字符:I0O7SJ,就是加密后的字符串(其实这个加密手段实在很容易破解,鉴于这是突然想出来的,那就^^)。但是,如果想加密的字符串实在太长,你能写一个程序完成这个任务吗?
Input
输入文件包含一行,欲加密的字符串。注意字符串的内容不一定是字母、数字,也可以是符号等。
Output
输出一行,为加密后的字符串。
方法:
安环状来读可以考虑我们以前用的一个算法叫 拆环为链 之后要我们求的是sa数组 注意输出的时候要考虑位置只能是前n个
代码:
#include<cstdio> #include<algorithm> #include<cstring> #include<iostream> #define M 200020 using namespace std; int tex[M], num[M], rank[M], sa[M], tmp[M], n, m, q; char s[M]; void qsort() { for(int i = 0; i <= m; i++) tex[i] = 0; for(int i = 1; i <= n; i++) tex[rank[tmp[i]]]++; for(int i = 1; i <= m; i++) tex[i] += tex[i - 1]; for(int i = n; i >= 1; i--) sa[tex[rank[tmp[i]]]--] = tmp[i]; } bool cmp(int l, int r, int wei) { return tmp[l] == tmp[r] && tmp[l + wei] == tmp[r + wei]; } void suffix() { for(int i = 1; i <= n; i++) rank[i] = num[i], tmp[i] = i; m = 127; qsort(); for(int w = 1; q < n; w <<= 1, m = q) { q = 0; for(int i = n - w + 1; i <= n; i++) tmp[++q] = i; for(int i = 1; i <= n; i++) if(sa[i] > w) tmp[++q] = sa[i] - w; qsort(); swap(tmp, rank); rank[sa[1]] = q = 1; for(int i = 2; i <= n; i++) if(cmp(sa[i], sa[i - 1], w)) rank[sa[i]] = q; else rank[sa[i]] = ++q; } } int main() { scanf("%s", s); n = strlen(s); for(int i = 1; i <= n; i++) num[i] = num[i + n] = s[i - 1], s[i + n - 1] = s[i - 1]; n += n; suffix(); for(int i = 1; i <= n; i++) { if(sa[i] <= (n >> 1)) putchar(s[sa[i] + (n >> 1) - 2]); } return 0; }