前言

听老师讲之前听说是字符串,比较虚(因为一直认为这东西很抽象)。。听后才发现只要认真听还是不难的。。

引入

读入一个长度为 n n n 的由大小写英文字母或数字组成的字符串,请把这个字符串的所有非空后缀按字典序从小到大排序,然后按顺序输出后缀的第一个字符在原串中的位置。位置编号为 1 1 1 到 n n n。

思路

法一:暴力 O ( n 2 l o g 2 ( n ) ) O(n^2log_2(n)) O(n2log2​(n))。
法二:在 s o r t sort sort 中用二分+Hash, O ( n l o g 2 ( n ) ) O(nlog^2(n)) O(nlog2(n))。
法三:SA。

正题

定义 s a [ i ] sa[i] sa[i] 为排名为 i i i 的后缀的起始位置的下标。

试想一下,我们怎么判断两个字符串谁大谁小?我们可以从最小位比起,将字典序高的放在前面,若前面的这一位 < 后面的这一位,则调换两个串。

我们每次比第 k k k 个位,可以用这个规则不断更新 s a [ i ] sa[i] sa[i]:将第 k k k 位设为第一关键字,则现在已经比好了 k k k 位之后的。将所有的第 k k k 位放进一个桶里,用基数排序的思想将这些进行排序,若第 k k k 位相等则比较第二关键字。那没有第 k k k 位怎么办呢(也可以理解为缺少第二关键字),我们就看成第二关键字为空,时间为 O ( n 2 ) O(n^2) O(n2)。

这时需要用 倍增 的思想优化。我们不用一位一位地设为第一关键字比,而是 k k k 位 k k k 位地设为关键字,每次 k < < = 1 k<<=1 k<<=1。将第一关键字放桶时,利用上一次算出来的 k k k 位后缀的大小关系,排好 2 k 2k 2k 长度的后缀,就一直这样重复即可。

代码

注: H e i g h t [ i ] Height[i] Height[i] 为以 i i i 开头的后缀与 排名为 k − 1 k-1 k−1 的后缀最大公共前缀( k k k 为 i i i 的排名)。其中有一个性质: H e i g h t [ i ] > = H e i g h t [ i − 1 ] − 1 Height[i] >= Height[i-1]-1 Height[i]>=Height[i−1]−1。利用这个性质,就可以用 O ( n ) O(n) O(n) 的时间解决这个问题。

总时间复杂度: O ( n l o g 2 ( n ) ) O(nlog_2(n)) O(nlog2​(n))。

#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <climits>
#include <iostream>
using namespace std;
const int MAXN = 3e5 + 5;
int n, c[MAXN], m = 200, hs[MAXN], sa[MAXN], tmp[MAXN], t[MAXN];
int Height[MAXN], mp[MAXN];
char s[MAXN];
//sa:当前排的顺序
//tmp:过渡序列
//hs:即 hash, 映射值
//c:统计个数
void write(int x) {if(x < 0) x = -x, putchar('-');if(x <= 9) {putchar(x + '0');return;}write(x / 10); putchar(x % 10 + '0');
}
void Clac_SA() {for(int i = 1; i <= n; i ++) hs[i] = s[i], c[hs[i]] ++;for(int i = 1; i <= m; i ++) c[i] += c[i - 1];for(int i = n; i >= 1; i --) sa[c[hs[i]] --] = i; // 排好第一轮 for(int i = 1; i <= n; i <<= 1) {int num = 0;for(int j = n - i + 1; j <= n; j ++) tmp[++ num] = j;for(int j = 1; j <= n; j ++) if(sa[j] > i) tmp[++ num] = sa[j] - i;// 转换为新的序列 (以第二关键字排序)for(int j = 1; j <= m; j ++) c[j] = 0;for(int j = 1; j <= n; j ++) c[hs[j]] ++;for(int j = 2; j <= m; j ++) c[j] += c[j - 1];for(int j = n; j >= 1; j --) sa[c[hs[tmp[j]]] --] = tmp[j];//注意要倒序 for(int j = 1; j <= n; j ++) t[j] = hs[j];hs[sa[1]] = 1; num = 1;for(int j = 2; j <= n; j ++) hs[sa[j]] = hs[sa[j - 1]] + (!((t[sa[j]] == t[sa[j - 1]]) & (t[sa[j] + i] == t[sa[j - 1] + i])));num = hs[sa[n]];if(num == n) break; // 可加可不加m = num; }
}
int Max(int x, int y) { return x > y ? x : y; }
void Clac_Height() {// 利用性质: H[i] >= H[i - 1] - 1 (注意 i 是下标,不是排名) for(int i = 1; i <= n; i ++) mp[sa[i]] = i; // sa[i] ~ n 的排名是 i  //sa:排名是 i 的为 sa[i] ~ n for(int i = 1; i <= n; i ++) {if(mp[i] == 1) {Height[i] = 0; continue;}Height[i] = Max(0, Height[i - 1] - 1);while(i + Height[i] <= n && sa[mp[i] - 1] + Height[i] <= n && s[i + Height[i]] == s[sa[mp[i] - 1] + Height[i]]) Height[i] ++;}
}
int main() {scanf("%s", s + 1); n = strlen(s + 1);Clac_SA(); Clac_Height();for(int i = 1; i <= n; i ++) write(sa[i] - 1), putchar(' ');putchar('\n');for(int i = 1; i <= n; i ++) write(Height[sa[i]]), putchar(' ');return 0;
}

例题

Long Long Message

题意

DM星人的基因种碱基有26种,即26个小写字母。
现在已知两个DM星人的基因序列,求你输出这两个DM星人的最长公共基因。
公共基因表示从两个基因序列的某个位置开始有一段完全相同的连续基因序列。

思路

将一个串接在另一个串的后面,跑SA,求出Height。 a n s = M a x { H e i g h t [ i ] } ans = Max\{Height[i]\} ans=Max{Height[i]}( i f if if i − 1 i-1 i−1 与 i i i不在一个串中)。为什么这样是对的呢?这里有一个贪心:字典序排序后的字符串,他们的最优解就在相邻的两个串之间。

代码
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <climits>
#include <iostream>
using namespace std;
const int MAXN = 1e6 + 5;
int n, c[MAXN], m = 200, hs[MAXN], sa[MAXN], tmp[MAXN], t[MAXN];
int Height[MAXN], mp[MAXN];
char s[MAXN], s2[MAXN];
//sa:当前排的顺序
//tmp:过渡序列
//hs:即 hash, 映射值
//c:统计个数
void write(int x) {if(x < 0) x = -x, putchar('-');if(x <= 9) {putchar(x + '0');return;}write(x / 10); putchar(x % 10 + '0');
}
void Clac_SA() {memset(c, 0, sizeof(c));for(int i = 1; i <= n; i ++) hs[i] = s[i], c[hs[i]] ++;for(int i = 1; i <= m; i ++) c[i] += c[i - 1];for(int i = n; i >= 1; i --) sa[c[hs[i]] --] = i; // 排好第一轮 for(int i = 1; i <= n; i <<= 1) {int num = 0;for(int j = n - i + 1; j <= n; j ++) tmp[++ num] = j;for(int j = 1; j <= n; j ++) if(sa[j] > i) tmp[++ num] = sa[j] - i;// 转换为新的序列 (以第二关键字排序)for(int j = 1; j <= m; j ++) c[j] = 0;for(int j = 1; j <= n; j ++) c[hs[j]] ++;for(int j = 2; j <= m; j ++) c[j] += c[j - 1];for(int j = n; j >= 1; j --) sa[c[hs[tmp[j]]] --] = tmp[j];//注意要倒序 for(int j = 1; j <= n; j ++) t[j] = hs[j];hs[sa[1]] = 1; num = 1;for(int j = 2; j <= n; j ++) hs[sa[j]] = hs[sa[j - 1]] + (!((t[sa[j]] == t[sa[j - 1]]) & (t[sa[j] + i] == t[sa[j - 1] + i])));num = hs[sa[n]];if(num == n) break; // 可加可不加m = num; }
}
int Max(int x, int y) { return x > y ? x : y; }
void Clac_Height() {// 利用性质: H[i] >= H[i - 1] - 1 (注意 i 是下标,不是排名) for(int i = 1; i <= n; i ++) mp[sa[i]] = i; // sa[i] ~ n 的排名是 i  //sa:排名是 i 的为 sa[i] ~ n for(int i = 1; i <= n; i ++) {if(mp[i] == 1) {Height[i] = 0; continue;}Height[i] = Max(0, Height[i - 1] - 1);while(i + Height[i] <= n && sa[mp[i] - 1] + Height[i] <= n && s[i + Height[i]] == s[sa[mp[i] - 1] + Height[i]]) Height[i] ++;}
}
int main() {scanf("%s%s", s + 1, s2 + 1);n = strlen(s + 1);int len = strlen(s2 + 1);s[n + 1] = '#'; n ++;for(int i = 1; i <= len; i ++) s[i + n] = s2[i]; n += len;Clac_SA(); Clac_Height();int ans = 0;for(int i = 2; i <= n; i ++) {if((long long)(sa[i - 1] - (n - len)) * (sa[i] - (n - len)) < 0) ans = max(ans, Height[sa[i]]);// 贪心 (注意会爆int)}printf("%d\n", ans);return 0;
}

[JSOI2007]字符加密

题面

喜欢钻研问题的JS 同学,最近又迷上了对加密方法的思考。一天,他突然想出了一种他认为是终极的加密办法:把需要加密的信息排成一圈,显然,它们有很多种不同的读法。

例如‘JSOI07’,可以读作: JSOI07 SOI07J OI07JS I07JSO 07JSOI 7JSOI0 把它们按照字符串的大小排序: 07JSOI 7JSOI0 I07JSO JSOI07 OI07JS SOI07J 读出最后一列字符:I0O7SJ,就是加密后的字符串(其实这个加密手段实在很容易破解,鉴于这是突然想出来的,那就^^)。 但是,如果想加密的字符串实在太长,你能写一个程序完成这个任务吗?

思路

版题。注意必须把这个字符串扩展一倍(因为是环),不然会被 zaba 这样的数据卡掉。

代码
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <climits>
#include <iostream>
using namespace std;
const int MAXN = 2e5 + 5;
int n, c[MAXN], m = 200, hs[MAXN], sa[MAXN], tmp[MAXN], t[MAXN];
int Height[MAXN], mp[MAXN];
char s[MAXN], s2[MAXN];
//sa:当前排的顺序
//tmp:过渡序列
//hs:即 hash, 映射值
//c:统计个数
void write(int x) {if(x < 0) x = -x, putchar('-');if(x <= 9) {putchar(x + '0');return;}write(x / 10); putchar(x % 10 + '0');
}
void Clac_SA() {memset(c, 0, sizeof(c));for(int i = 1; i <= n; i ++) hs[i] = s[i], c[hs[i]] ++;for(int i = 1; i <= m; i ++) c[i] += c[i - 1];for(int i = n; i >= 1; i --) sa[c[hs[i]] --] = i; // 排好第一轮 for(int i = 1; i <= n; i <<= 1) {int num = 0;for(int j = n - i + 1; j <= n; j ++) tmp[++ num] = j;for(int j = 1; j <= n; j ++) if(sa[j] > i) tmp[++ num] = sa[j] - i;// 转换为新的序列 (以第二关键字排序)for(int j = 1; j <= m; j ++) c[j] = 0;for(int j = 1; j <= n; j ++) c[hs[j]] ++;for(int j = 2; j <= m; j ++) c[j] += c[j - 1];for(int j = n; j >= 1; j --) sa[c[hs[tmp[j]]] --] = tmp[j];//注意要倒序 for(int j = 1; j <= n; j ++) t[j] = hs[j];hs[sa[1]] = 1; num = 1;for(int j = 2; j <= n; j ++) hs[sa[j]] = hs[sa[j - 1]] + (!((t[sa[j]] == t[sa[j - 1]]) & (t[sa[j] + i] == t[sa[j - 1] + i])));num = hs[sa[n]];if(num == n) break; // 可加可不加m = num; }
}
int Max(int x, int y) { return x > y ? x : y; }
void Clac_Height() {// 利用性质: H[i] >= H[i - 1] - 1 (注意 i 是下标,不是排名) for(int i = 1; i <= n; i ++) mp[sa[i]] = i; // sa[i] ~ n 的排名是 i  //sa:排名是 i 的为 sa[i] ~ n for(int i = 1; i <= n; i ++) {if(mp[i] == 1) {Height[i] = 0; continue;}Height[i] = Max(0, Height[i - 1] - 1);while(i + Height[i] <= n && sa[mp[i] - 1] + Height[i] <= n && s[i + Height[i]] == s[sa[mp[i] - 1] + Height[i]]) Height[i] ++;}
}
int main() {scanf("%s", s + 1);n = strlen(s + 1);for(int i = n + 1; i <= (n << 1); i ++) s[i] = s[i - n]; n <<= 1;Clac_SA(); Clac_Height();for(int i = 1; i <= n; i ++) {if(sa[i] <= n / 2) printf("%c", s[sa[i] + n / 2 - 1]);}return 0;
}

后缀数组(SA)倍增法总结相关推荐

  1. 后缀数组(倍增)学习记录,我尽可能详细的讲了

    后缀数组(倍增) 后缀数组 后缀数组能干什么 一些基本概念 那么到底怎么排序呢? 倍增排序 具体执行排序呢? 基数排序 关于排序的桶 关于桶排序在字符串倍增中的嵌入 具体改执行的排序事情 倍增排序的代 ...

  2. 长字符串匹配(BWT编码、后缀数组、倍增算法、FM索引)

    用 O(m) 时间复杂度找出一个长度为 m 的短字符串在一个长度为 n 的长字符串中的精确匹配(n>>m),限制长短字符串仅由 A.C.G.T 这四种字符组成. 输入:长短字符串 输出:短 ...

  3. POJ 3581 Sequence ——后缀数组 最小表示法

    [题目分析] 一见到题目,就有了一个显而易见obviously的想法.只需要每次找到倒过来最小的那一个字符串翻转就可以了. 然而事情并不是这样的,比如说505023这样一个字符串,如果翻转了成为320 ...

  4. 后缀数组的倍增算法(Prefix Doubling)

    最近在自学BWT算法(Burrows-Wheeler transform),其中涉及到对字符串循环移位求编码.直观的办法就是模拟,使用O(n3)的时间求出BWT编码.经过简单的简化后也要O(n2log ...

  5. [SDOI2016] 生成魔咒(后缀数组SA + st表 + set)动态不同子串个数

    problem luogu-P4070 魔咒串由许多魔咒字符组成,魔咒字符可以用数字表示.例如可以将魔咒字符 1,21,21,2 拼凑起来形成一个魔咒串 [1,2][1,2][1,2]. 一个魔咒串 ...

  6. 后缀数组+贪心+隔板法

    题意:给你sa数组,就是每个排名的后缀开始下标,让你求有多少种满足要求的串,有那么一个原理,对于相邻排名的两个后缀,后缀i的首字母要不要大于i-1,的取决于,第二个字符的比较,如果i的第二个字符> ...

  7. 后缀数组(SA)备忘

    两个让我真正理解代码的资料: 2009集训队论文 网上的经典应用都是从里面抄的,还把解释给去掉了...真事屑 这篇博客 代码注释特别好 桶排换成快排的代码,便于理解算法思想 ,这里面要减去k的原因是 ...

  8. 洛谷P2463 [SDOI2008]Sandy的卡片(后缀数组SA + 差分 + 二分答案)

    题目链接:https://www.luogu.org/problem/P2463 [题意] 求出N个串中都出现的相同子串的最长长度,相同子串的定义如题:所有元素加上一个数变成另一个,则这两个串相同,可 ...

  9. 字符串-后缀树和后缀数组详解

    文章目录 后缀树 后缀数组 概念 sa[] rk[] height[] 例题 HDU-1403最长公共子串 洛谷P2408 不同子串个数 HDU-5769Substring 后缀树 建议先了解一下字典 ...

  10. 后缀数组 + Hash + 二分 or Hash + 二分 + 双指针 求 LCP ---- 2017icpc 青岛 J Suffix (假题!!)

    题目链接 题目大意: 就是给你n个串每个串取一个后缀,要求把串拼起来要求字典序最小!! sum_length_of_n≤5e5sum\_length\_of\_n\leq 5e5sum_length_ ...

最新文章

  1. 普及vmware连接上网
  2. linux安装phoenix 5.1.0(对应hbase 2.2.6)
  3. 定义一个计算字符串有效长度的_一个正方形的小抽屉柜,根据设计草图计算出所需四片木板的长度...
  4. [mybatis]动态sql_if_where_trim判断OGNL
  5. MySQL中my.cnf解析
  6. python使用的一些小事儿
  7. 网页不够惊艳?优秀案例给你灵感
  8. 渗透测试——XP工具练习
  9. android是j2me的一个实现吗,j2me与android的区别
  10. 计算机原理的教学,计算机组成原理教学方法探析
  11. 爱思助手更新后无法连接服务器,爱思助手无法连接手机怎么办 爱思助手连接失败问题解决办法...
  12. win11自带杀毒软件怎么关闭 windows11关闭自带杀毒软件的步骤
  13. 使用曲面细分渲染毛发
  14. 股票交易一点感悟和程序化交易实战
  15. python面试 --基础题
  16. 数学论文(优化方向)写作总结
  17. 【生信】使用QIIME进行 进化树,Alpha,Beta多样性 分析
  18. bugku 黄道十二官
  19. mysql数据库与access数据库连接_JDBC连接Access数据库的几种方式
  20. 物联网竞赛单片机应用开发-项目汇总

热门文章

  1. nyoj-586 疯牛,c++,详解
  2. 【C#语言】MDI窗体
  3. 创业必看——开公司的流程
  4. Win7系统怎么删除休眠文件?
  5. 我教你怎么擒老鼠-给有电脑的朋友一个建设
  6. 报错:DropDownList1”有一个无效 SelectedValue,因为它不在项目列表中
  7. Mybatis传入参数类型不匹配导致的报错:
  8. 安装bugfree进行环境检查时,提示mysql未安装
  9. C语言循环练习,建议练练手
  10. WCF--提示:异常消息为“传入消息的消息格式不应为“Raw”。此操作的消息格式应为 'Xml', 'Json'。...