后缀数组

三步学通KMP

痴心易碎 提交于 2020-02-24 03:16:02
前言 谈到字符串模式匹配算法,莫过于最经典的KMP算法,它由D.E.Knuth,J.H.Morris和V.R.Pratt三位大牛于1977年联合发表提出的,因此人们称它为克努特—莫里斯—普拉特操作(简称KMP算法),KMP算法可以在O(n+m)的时间复杂度以内完成字符串的匹配操作,其核心思想在于:当一趟匹配过程中出现字符不匹配时,不需要回溯主串的指针,而是利用已经得到的“部分匹配”,将模式串尽可能多地向右“滑动”一段距离,然后继续比较。 说到KMP,也得需要提一下字符串的模式匹配的意义,这也是我们学习KMP及各种字符串匹配算法的意义。字符串的模式匹配是对字符串的基本操作之一,广泛应用于生物信息学、信息检索、拼写检查、语言翻译、数据压缩、网络入侵检测等领域,如何简化其复杂性一直是算法研究中的经典问题。字符串的模式匹配实质上就是寻找模式串T是否在主串S 中,及其出现的位置。由于对字符串匹配的效率要求越来越高, 所以需要不断地改良模式匹配算法,减少其时间复杂度。 说到KMP,也感觉到数学世界的美妙,我们的祖先早在2000多年前就用阴阳(0,1)虚拟的分割了这个世界,而西方近代却用0、1(阴阳)在计算机里虚拟合成了这个世界;还有数学里的那些很奇怪而有趣的问题,比如杨辉三角, Kaprekar 常数,“421陷阱”等,不知道是不是与它们最终都能转化为0、1(阴阳)有关。好了,言归正传

P2870 后缀数组

两盒软妹~` 提交于 2020-02-22 16:45:21
题目传送门 题意: 字符串 初始是空串。 给你一个字符串 ,可以选择从串首或串尾拿走一个字符,把这个字符拼接到 的尾部。 输出最小字典序的 。 数据范围:字符串长度不超过 。 题解: 当前串首和串尾的字符不同,那么选择字典序小的那个字符就好了。 如果串首和串尾的字符相同,设串首字符是 ,串尾字符是 。 我们需要比较内层字符的大小,即比较 和 的大小。 如果还相同,那就继续向内层比较,直到找到不同的字符,如果一直相同,那选串首和选串尾是一样的。 暴力做是 。因为每次选字符时要向内层比较的次数是 。选 次那就是 。 考虑后缀数组优化。 我们把 的正序和 的倒序拼接成字符串 。 举例子: ,那么 。 模拟一遍就知道是怎么优化的了。 字符串下标从 开始,初始时 。 现在 ,然后我们比较 和 的大小关系。 其实我们就是在比较 和 的大小关系。这个后缀数组预处理后,就可以 比较了。 时间复杂度: 。 感受: 想不到用后缀数组。 并且题面的数据范围是错的。我开50W一直RE。 代码: #include<bits/stdc++.h> using namespace std ; typedef long long ll ; const int maxn = 1e6 + 5 ; int rk[maxn << 1] , sa[maxn << 1] , height[maxn << 1] ; int

P4051 后缀数组

家住魔仙堡 提交于 2020-02-22 16:07:21
题目传送门 题意: 给你一个字符串,输出加密后的字符串。 例如‘JSOI07’,可以读作: JSOI07 SOI07J OI07JS I07JSO 07JSOI 7JSOI0 把它们按照字符串的大小排序: 07JSOI 7JSOI0 I07JSO JSOI07 OI07JS SOI07J 读出最后一列字符:I0O7SJ,就是加密后的字符串。 数据范围:字符串的长度不超过 。 题解: 给一个字符串 ,我们扩大二倍,变成 。 然后考虑后缀排序,输出想要的东西。 设 。 那么当后缀长度大于 时,后面多出的部分不会影响排序。 简单分析一下: 设 ,则 。 现在有一个后缀是 。 我们想要的其实是 ,分析最后的三个字符 的影响。 观察发现 是 的前缀,那也就是说后面不想要的部分是想要的部分的前缀,那就由自身决定了,那就没影响了。 然后掏出后缀数组板子,求出sa就行了。 感受: 反复看了几遍都是5个0,结果1e5是RE,开成1e6才过去。 代码: #include<bits/stdc++.h> using namespace std ; typedef long long ll ; const int maxn = 1e6 + 5 ; int rk[maxn << 1] , sa[maxn << 1] , height[maxn << 1] ; int tmp[maxn << 1] , cnt

KMP算法详解

旧街凉风 提交于 2020-02-20 01:53:48
KMP算法得核心就是对PMT得理解,下面介绍PMT: 大家只需要记住一点,PMT是什么东西。然后自己临时推这个算法也是能推出来的,完全不需要死记硬背。KMP算法的核心,是一个被称为部分匹配表(Partial Match Table)的数组。我觉得理解KMP的最大障碍就是很多人在看了很多关于KMP的文章之后,仍然搞不懂PMT中的值代表了什么意思。这里我们抛开所有的枝枝蔓蔓,先来解释一下这个数据到底是什么。对于字符串“abababca”,它的PMT如下表所示: 就像例子中所示的,如果待匹配的模式字符串有8个字符,那么PMT就会有8个值。 我先解释一下字符串的前缀和后缀。如果字符串A和B,存在A=BS,其中S是任意的非空字符串,那就称B为A的前缀。例如,”Harry”的前缀包括{”H”, ”Ha”, ”Har”, ”Harr”},我们把所有前缀组成的集合,称为字符串的前缀集合。同样可以定义后缀A=SB, 其中S是任意的非空字符串,那就称B为A的后缀,例如,”Potter”的后缀包括{”otter”, ”tter”, ”ter”, ”er”, ”r”},然后把所有后缀组成的集合,称为字符串的后缀集合。要注意的是,字符串本身并不是自己的后缀。 有了这个定义,就可以说明PMT中的值的意义了。 PMT中的值是字符串的前缀集合与后缀集合的交集中最长元素的长度 。例如,对于”aba”,它的前缀集合为

后缀排序

ぐ巨炮叔叔 提交于 2020-02-19 16:31:25
Tim正在自学《数据结构》,他刚刚学会如何比较两个字符串大小。书上是这么说的(和Pascal语言中的比较规则相同,学习过Pascal语言的同学可以跳过这段): 比较两个不同字符串s1=’p1p2p3…pN’和s2=’q1q2q3…qM’的大小,设N<=M。 若s1是s2的前缀,则s1qi,且i最小;若pis2。 Tim想通过练习熟练运用这个规则,于是打算出许多字符串,并将它们从小到大排序。可是Tim非常懒,随机写出 K个很长的字符串实在是太麻烦了。不过聪明的他想到了一个好办法,他写了一个很长的字符串,自言自语说,“我只要把这个字符串的所有后缀从小到大排序就可以了”。 Input 仅有一行,且是一个仅包含小写字母的字符串,长度K不超过10^5。 Output 有K行,每行一个数字,第i行的数字Pi表示所有后缀中,第i小的是由原字符串第Pi个字符引导的后缀。 Sample Input mississippi Sample Output 11 8 5 2 1 10 9 7 4 6 3 Sol: sa[i]=j...排名第i的后缀是从原串中的第j个位置开始的 rank[i]=j...原串中第i个位置开始的后缀,排名为j 两者是个互逆的函数,整个程序就是两个数组倒来倒过 #include<cstdio> #include<iostream> #include<cmath> #include

后缀自动机专题

一笑奈何 提交于 2020-02-17 15:49:10
如果不算pre指针的话后缀自动机就是一个DAG,这是它能很方便地进行dp的前提。 而pre指针返回什么呢,返回的就是上一个的前缀包含改结点所代表子串的那个后缀,和AC自动机上的fail指针很像,都是为了匹配。我目前学得不深,看不出和AC自动机的fail指针有什么区别,用起来也几乎一样。 相比于字典树和回文树,后缀自动机每个结点会有多个父结点,可以表示多种子串(从根节点到它的每条路径都是一个子串),因此子串的信息只能在路径中记录,比如长度,而该子串说记录的长度step,则是根结点到它的最远距离,而某个子串的长度就是该代表该子串的路径的长度了,并不一定是某个结点的step。 spoj1811 求两个串的最长公共子串的长度。 对A串建立后缀自动机,对B串进行匹配,如果匹配失败,沿着失败指针往回走到第一个能匹配的位置继续匹配(看起来似曾相识?没错,这不是AC自动机的过程吗。。。),当然如果到根节点还不能继续匹配,那就只有从头再来了。这里随时记录长度更新答案即可。 当然后缀数组也可以做,把两个串拼接起来,求lcp即可。。。然后后缀自动机好快。。。。在spoj上居然60ms过了。。。 #include<bits/stdc++.h> #define REP(i,a,b) for(int i=a;i<=b;i++) #define MS0(a) memset(a,0,sizeof(a))

后缀数组 代码详解

此生再无相见时 提交于 2020-02-17 02:14:22
研究了好几天的后缀数组,今天终于是把代码实现给看明白了了。 大多数博客都讲解了了后缀数组以及倍增法,但是对于代码的讲解不是很明白。 这篇就把LRJ蓝书上的代码拆开来一句一句的进行解释。 关于倍增算法我建议去看lrj的数,写的非常清晰明了;基数排序可以去看看百度百科,其实和桶排序是一家子。 代码: char s[maxn]; int sa[maxn],t[maxn],t2[maxn],c[maxn],n; void print(int *arr){ for(int i=0;i<n;i++) cout<<arr[i]<<" "; cout<<endl; } void getSa(int m){ int *x=t,*y=t2; for(int i=0;i<m;i++) c[i]=0; for(int i=0;i<n;i++) c[x[i]=s[i]]++; for(int i=1;i<m;i++) c[i]+=c[i-1]; for(int i=n-1;i>=0;i--) sa[--c[x[i]]]=i; cout<<"sa:";print(sa); cout<<"x:";print(x); for(int k=1;k<=n;k<<=1){ int p=0; for(int i=n-k;i<n;i++) y[p++]=i; for(int i=0;i<n;i++) if(sa[i]>=k

后缀数组

眉间皱痕 提交于 2020-02-17 02:12:07
看了好久,吐了,这个东西打脑阔得很。 首先得介绍一下 基数排序 //#include<bits/stdc++.h> # include <iostream> # include <cstdio> using namespace std ; //基数排序:一种稳定的,非比较的排序方式。分为LSD和MSD两种方式,LSD通过从最低为开始,MSD从高位开始排, //将所有数的同一位分配到桶中,并通过合并桶的方式进行一个部分排序,持续这样的操作直到最高位 int getmax ( int a [ ] , int n ) { int res = 1 , p = 10 ; for ( int i = 0 ; i < n ; ++ i ) { while ( a [ i ] >= p ) { res ++ , p * = 10 ; } } return res ; } int main ( ) { int a [ 1000 ] , count [ 1000 ] , tmp [ 1000 ] ; int radix = 1 ; const int N = 10 ; for ( int i = 0 ; i < N ; ++ i ) scanf ( "%d" , & a [ i ] ) ; int maxbit = getmax ( a , N ) ; //cout<<maxbit<<endl;

后缀数组

柔情痞子 提交于 2020-02-12 12:08:28
后缀是从字符串的某个位置到字符串末尾的非空子串。例如:$suff(HORSE) = \{E, SE, RSE, ORSE, HORSE\}$。 后缀数组是包含字符串所有已排序后缀的数组。例如:$sa(CAMEL) = \{1-AMEL, 0-CAMEL, 3-EL, 4-L, 2-MEL\} = \{1, 0, 3, 4, 2\}$。 首先可以想出一种基于快速排序的后缀数组求法,但是时间复杂度为$O(n^2logn)$。可用一种倍增算法来优化,时间复杂度$O(nlogn)$,待补。 后缀数组用于事先不知道模板P的多模板匹配问题,此时需要处理文本串T。 sa是后缀排名到位置的映射 x是第一关键字位置到排名(数值)的映射 y是第二关键字排名到位置的映射 c是基数排序的桶 例题:洛谷P3809 #include<bits/stdc++.h> using namespace std; const int N = 1000050; char s[N], p[N]; int sa[N], x[N], y[N], c[N], n; void build_sa(int m) { n = strlen(s + 1); for(int i = 1; i <= n; i++) c[x[i] = s[i]]++; for(int i = 2; i <= m; i++) c[i] += c[i-1];

[BJWC2018]Border 的四种求法(后缀自动机+链分治+线段树合并)

◇◆丶佛笑我妖孽 提交于 2020-02-09 10:04:10
题目描述 给一个小写字母字符串 S ,q 次询问每次给出 l,r ,求 s[l..r] 的 Border 。 Border: 对于给定的串 s ,最大的 i 使得 s[1..i] = s[|s|-i+1..|s|], |s| 为 s 的长度。 题解 这题的描述很短,给人一种很可做的假象。 暴力1:每次对区间lr做一次KMP,求出border数组,复杂度nq。 暴力2:构建后缀自动机,用线段树合并维护出right集合考虑到两个串的最长后缀为他们在parent树上的LCA的len,所以我们可以在parent树上跳father,相当于枚举LCA,那么如果有匹配的点,则一定满足: i-len[LCA]+1<=l => i=len[LCA]<l => i<l+len[LCA] i>=l&&i<r (i为我们要求的点,l为询问的左端点,LCA为i和r的LCA) 所以我们只需要在线段树上查子树内查询满足上述条件的最大的i就可以了,复杂度最好qlogn,最差qn。 这两种暴力好像差不多。。 我们观察到第二种暴力它的瓶颈在于枚举LCA,但查询只需要一个log,我们可以想一些办法把复杂度均摊一下。 链分治 这就是这道题的重头戏,它用到了一个重要的性质,我们将一棵树重链剖分之后,从根到任意一点的路径上,轻重链切换的次数是不超过log的。 然后我们就可以用它搞一些事情。 比如说这道题