后缀数组

【后缀数组】【LuoguP4051】 [JSOI2007]字符加密

百般思念 提交于 2019-12-05 17:48:13
题目链接 题目描述 喜欢钻研问题的JS 同学,最近又迷上了对加密方法的思考。一天,他突然想出了一种他认为是终极的加密办法:把需要加密的信息排成一圈,显然,它们有很多种不同的读法。 例如‘JSOI07’,可以读作: JSOI07 SOI07J OI07JS I07JSO 07JSOI 7JSOI0 把它们按照字符串的大小排序: 07JSOI 7JSOI0 I07JSO JSOI07 OI07JS SOI07J 读出最后一列字符:I0O7SJ,就是加密后的字符串(其实这个加密手段实在很容易破解,鉴于这是突然想出来的,那就^^)。 但是,如果想加密的字符串实在太长,你能写一个程序完成这个任务吗? 说明 对于40%的数据字符串的长度不超过10000。 对于100%的数据字符串的长度不超过100000。 思路 一看到子串排序,就能想到后缀数组 将原串复制一遍接到后面,然后就没了 代码 #include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define maxn 200010 using namespace std; int n, m; char s[maxn]; int tax[maxn], rk[maxn], tp[maxn], sa[maxn], M = 200; void rsort() {

字符串与模式匹配算法(四):BM算法

a 夏天 提交于 2019-12-04 14:20:38
一、BM算法介绍   BM算法(Boyer-Moore算法)是罗伯特·波义尔(Robert Boyer)和杰·摩尔(J·Moore)在1977年共同提出的。与KMP算法不同的是,BM算法是模式串P由左向右移动,而字符的比较时由右向左进行。当文本字符与模式不匹配时,则根据预先定义好的“坏字符串偏移函数”和“好后缀偏移函数”计算出偏移量。   坏字符偏移和好后缀偏移这两个函数胡的原理可以简单描述为:当比较工作进行到文中的j处时,新一轮的比较从右向左进行。不相匹配的情况出现在模式字符串的P[i]=a以及目标字符串的T[i+j]=b处,那么这时有T[i+j+1,...,j+m-1]=P[i+1,...,m-1]且P[i]≠T[i+j]。   由好后缀偏移函数确定的偏移量分两种情况。首先,将模式字符串与目标字符串进行从右向左比较,当比较到字符P[i]时发现不同,则把T[i+j+1,……,j+m-1]=P[i+1],……,m-1]这一段看成u。如果在模式字符串里面u再次出现,且u的左侧是一个不同于a的前缀c时,则将模式字符串最右端再次出现的且前缀不是a的片段与文本串T[i+j+1,……,j+m-1]对齐,并将这次移动的偏移量存储在bmGs[i]中。    其次,如果u在模式中没有再次出现,或者再次出现但u前面的字符是a,那么就将片段T[i+j+1,……,j+m-1

[BZOJ4310] 跳蚤 - 后缀数组,二分,ST表

血红的双手。 提交于 2019-12-03 11:18:53
[BZOJ4310] 跳蚤 Description 首先,他会把串分成不超过 \(k\) 个子串,然后对于每个子串 \(S\) ,他会从 \(S\) 的所有子串中选择字典序最大的那一个,并在选出来的 \(k\) 个子串中选择字典序最大的那一个。他称其为“魔力串”。现在他想找一个最优的分法让“魔力串”字典序最小。 Solution 我们将某个子串在所有子串(本质不同)中按照字典序的排名称作它的字典序排名。思考如何使用后缀数组快速求出一个子串的字典序排名。 假设我们要求 \(s[l,r]\) 这个子串的字典序排名,我们要在后缀排序中,从 \(r[l]\) 位置开始,向左找到第一个 \(h[i]<r-l+1\) 的位置,这个过程可以用二分 + ST表完成,那么 \(ra[i]-(n-sa[i]+1)+(r-l+1)\) 就是它的排名。 二分最终魔力串的字典序排名。我们倒着将整个串的字符一个个从前面插入,维护当前串的排名,如果当前串排名大于魔力串排名,那么这个当前串就危险了,我们要从它的第一个字符后面切开一刀。 这样我们只需要比较切的次数和 \(k\) 的关系,就可以调整二分边界。时间复杂度 \(O(nlogn)\) 我可真是个奥比啊 输出个方案挂了十几发 #include <bits/stdc++.h> using namespace std; #define int long long

[BZOJ3277/BZOJ3473] 串 - 后缀数组,二分,双指针,ST表,均摊分析

泄露秘密 提交于 2019-12-03 10:52:51
[BZOJ3277] 串 Description 现在给定你n个字符串,询问每个字符串有多少子串(不包括空串)是所有n个字符串中至少k个字符串的子串(注意包括本身)。 Solution 首先将所有串连接起来,预处理出后缀数组和高度数组。 显然直接主席树可以很容易做到 \(O(n \log^2 n)\) 。对于每一个后缀的位置,二分一个 LCP 长度,找到这个 LCP 长度对应的区间,检查这个区间是否合法来调节二分边界。 注意在这个做法里,瓶颈不在于主席树,因为主席树的功能完全可以用双指针预处理一个数组来替代。瓶颈在于,实质上使用了一个二分套二分的做法。 但我们有更好的做法。 引理:按照原始顺序,如果第 \(i\) 个后缀有 \(x\) 个前缀能被 \(k\) 个串包含,那么第 \(i+1\) 个后缀至少有 \(x-1\) 个前缀能被 \(k\) 个串包含。 那么我们先用双指针预处理 \(jmp[i]\) 代表按照后缀排序,最大的 \(j\) 使得 \([j,i]\) 这个后缀区间合法。 到第 \(i\) 个后缀的时候我们就从后缀 \(i-1\) 的答案开始向上枚举,用二分+ST表找出它左右边第一个高度比当前枚举值小的位置,判断这个区间的合法性来决定是否继续枚举,均摊时间复杂度 \(O(nlogn)\) 。 \(O(\log n)\) 解法 #include <bits/stdc++

浅谈后缀数组SA

不打扰是莪最后的温柔 提交于 2019-12-03 08:28:23
这篇博客不打算讲多么详细,网上关于后缀数组的blog比我讲的好多了,这一篇博客我是为自己加深印象写的。 给你们分享了那么多,容我自私一回吧~ 参考资料:这位dalao的 blog 一、关于求SuffixArray的一些变量定义: 1. sa[i]=j,表示第i名的后缀从j开始 **存的是下标** 2. rnk[i]=j,从i开始的后缀是第j名的 **与sa为互逆运算,存的是值* * 3. tp[i]=j, 第二关键字为i的后缀从j开始 **可理解为第二关键字的SA,存的是下标** 插入解释一下第一关键字和第二关键字: 我们要对所有的后缀进行排序,怎么排呢? 开始时,我们每个字符的后缀存的只有它自己,所以它后缀的大小就是它的 ASCII码。 我们把每个字符i看成(s[i],i)的二元组,如果我们直接丢pair<int,int>里面然后std::sort, 这样的时间复杂度是O(log^2 n)的,显然不够优秀。 所以就需要用到基数排序RadixSort,不了解的自行百度。 再使用倍增法,就可以使我们 排序 的时间复杂度降低到O(logn)。 所以我们要对每个后缀的前两个字母进行排序,第一个字母的相对关系已经得到了。 第i个后缀的第二个字母,就是第i+1个后缀的第一个字母,利用这个关系我们第二个字母的相对关系也就知道了。 我们的tp数组就是用来记录它的,rnk[i]表示 上一轮

[TJOI2017] DNA - 后缀数组,稀疏表

点点圈 提交于 2019-12-03 02:11:58
[TJOI2017] DNA Description 求模式串与主串的匹配次数,容错不超过三个字符。 Solution 枚举每个开始位置,进行暴力匹配,直到失配次数用光或者匹配成功。考虑到容错量很小,所以每个位置开始的匹配过程中大部分与普通匹配是同样操作,而我们需要的其实就是 LCP 长度,所以预处理出后缀数组和高度数组,建 ST 表支持 RMQ 询问,来加速暴力匹配的过程。时间复杂度 \(O(n \log n)\) #include <bits/stdc++.h> using namespace std; int n,k,l0,m=256,sa[250005],y[250005],u[250005],v[250005],o[250005],r[250005],h[250005],T; char str[250005]; int lg2[250005]; struct clsst { int a[250005][21]; void build(int *src,int n) { for(int i=1;i<=n;i++) a[i][0]=src[i]; for(int i=1;i<=20;i++) for(int j=1;j<=n-(1<<i)+1;j++) a[j][i]=min(a[j][i-1],a[j+(1<<(i-1))][i-1]); } int query(int l

[AHOI2013] 差异 - 后缀数组,单调栈

梦想的初衷 提交于 2019-12-03 02:05:51
[AHOI2013] 差异 Description 求 \(\sum {len(T_i) + len(T_j) - 2 lcp(T_i,T_j)}\) 的值 其中 \(T_i (i = 1,2,...,n)\) 为后缀串 \(S[i,n]\) Solution 单调栈乱扫一发即可。 始终维护当前栈内元素的和,然后加进答案里。 #include <bits/stdc++.h> using namespace std; #define int long long int n,m=256,sa[1000005],y[1000005],u[1000005],v[1000005],o[1000005],r[1000005],h[1000005],T; char str[1000005]; int ans = 0, sum = 0; int sta[1000005],stap[1000005],top=0; signed main(){ cin>>str+1; n=strlen(str+1); for(int i=1;i<=n;i++) u[str[i]]++; for(int i=1;i<=m;i++) u[i]+=u[i-1]; for(int i=n;i>=1;i--) sa[u[str[i]]--]=i; r[sa[1]]=1; for(int i=2;i<=n;i++) r[sa[i

课程设计

匿名 (未验证) 提交于 2019-12-03 00:30:01
#include <stdio.h> #include <stdlib.h> #define MAXSIZE 100 #define OK 1 #define ERROR -1 #define TRUE 1 #define FALSE 0 typedef int Status; typedef struct { char data[MAXSIZE]; int top; //栈顶位置 }Stack; //这个栈用来进行中缀转后缀过程中暂存运算符 typedef struct { float data[MAXSIZE]; int top; }ValueStack; //这个栈用来在后缀表达式求值时存放操作数 //1.栈的初始化 Status InitStack(Stack *s, int n){ if (n>MAXSIZE || n< 1 ){ printf ( "输入的长度有误!\n" ); return ERROR; } int i; for (i= 0 ;i<n;i++){ s->data[i] = 'a' ; } s->top = 0 ; return OK; } Status InitValueStack(ValueStack *v, int n){ if (n>MAXSIZE || n< 1 ){ printf ( "输入的长度有误!\n" ); return ERROR;

BM算法详解

匿名 (未验证) 提交于 2019-12-03 00:22:01
原帖链接: BM算法详解 来源 在没有BM算法时,其原始算法是从后往前进行匹配,需要两层循环,判断以某个字符为结尾的子串是否和模式串相等,这种算法也称作暴搜; 贴上代码: void BLS( string s, string p) { int s_len = s. size (), p_len = p. size (); int j = 0 , i = 0 ; while (j <= s_len - p_len) { for (i = p_len - 1 ; i >= 0 && p[i] == s[i + j]; --i) {} if (i < 0 ) { cout << "match: " << i + j + 1 << endl; j += p_len; } else j++; } } 算法的思想还是比较容易理解的,i和j分别指的是,模式串中已经匹配的位数,模式串相对于原串移动的位数; 算法包含了两个重要的内容,分别是好后缀和坏字符的规则; 坏字符 :当模式串和原串的字符并不匹配时,原串中的字符就称为坏字符; 好后缀 :模式串和原串的字符相等时所有的字符串,比如ABCD和BCD,那么它的好后缀则包括第一次匹配的D,和第二次匹配的CD,还有第三次的BCD; 例子: BM算法的向右移动模式串的距离就是取坏字符和好后缀算法得到的最大值; 这两个内容分别拥有着几条规则,需要注意:

KMP算法的原理(绝对简单易懂)

匿名 (未验证) 提交于 2019-12-03 00:19:01
KMP算法的作用 比较一个字符串是否为另一个字符串的子串,并可以返回匹配首位置 算法实现过程 1. 首先,字符串"BBC ABCDAB ABCDABCDABDE"的第一个字符与搜索词"ABCDABD"的第一个字符,进行比较。因为B与A不匹配,所以搜索词后移一位。 2. 因为B与A不匹配,搜索词再往后移。 3. 就这样,直到字符串有一个字符,与搜索词的第一个字符相同为止。 4. 接着比较字符串和搜索词的下一个字符,还是相同。 5. 直到字符串有一个字符,与搜索词对应的字符不相同为止。 6. 这时,最自然的反应是,将搜索词整个后移一位,再从头逐个比较。这样做虽然可行,但是效率很差,因为你要把"搜索位置"移到已经比较过的位置,重比一遍。 7. 一个基本事实是,当空格与D不匹配时,你其实知道前面六个字符是"ABCDAB"。KMP算法的想法是,设法利用这个已知信息,不要把"搜索位置"移回已经比较过的位置,继续把它向后移,这样就提高了效率。 8. 怎么做到这一点呢?可以针对搜索词,算出一张《部分匹配表》(Partial Match Table)。这张表是如何产生的,后面再介绍,这里只要会用就可以了。 9. 已知空格与D不匹配时,前面六个字符"ABCDAB"是匹配的。查表可知,最后一个匹配字符B对应的"部分匹配值"为2,因此按照下面的公式算出向后移动的位数:    移动位数 = 已匹配的字符数