uoj 35 后缀数组first blood
来源:互联网 发布:央视揭网络卖淫产业链 编辑:程序博客网 时间:2024/06/05 08:27
传送门
- 后缀树的用途,总结起来大概有如下几种:
(1). 查找字符串o是否在字符串S中。
方案:用S构造后缀树,按在trie中搜索字串的方法搜索o即可。
原理:若o在S中,则o必然是S的某个后缀的前缀。
例如S: leconte,查找o: con是否在S中,则o(con)必然是S(leconte)的后缀之一conte的前缀.有了这个前提,采用trie搜索的方法就不难理解了。
(2). 指定字符串T在字符串S中的重复次数。
方案:用S+’$'构造后缀树,搜索T节点下的叶节点数目即为重复次数
原理:如果T在S中重复了两次,则S应有两个后缀以T为前缀,重复次数就自然统计出来了。
(3). 字符串S中的最长重复子串
方案:原理同2,具体做法就是找到最深的非叶节点。
这个深是指从root所经历过的字符个数,最深非叶节点所经历的字符串起来就是最长重复子串。
为什么要非叶节点呢?因为既然是要重复,当然叶节点个数要>=2。
(4). 两个字符串S1,S2的最长公共部分
方案:将S1#S2$作为字符串压入后缀树,找到最深的非叶节点,且该节点的叶节点既有#也有$(无#)。
#include <cstdio>#include <cstdlib>#include <cstring>#include <algorithm>using namespace std;char s[101000];int sa[101000], t[101000], t2[101000], c[101000];int Rank[101000], height[101000];// 对后缀的第一个字符进行基数排序,m 表示名次的最大值。//SA[ ]:它的下标就是后缀排名//c[ ]:用来基数排序。初始值恰好是每种字符出现的次数。//后来它的作用就跟基数排序密切相关,建议学习基数排序/*for(int i=0;i<len;i++) m=m>s[i]?m:s[i];*/inline void build_sa(int m){ int *x = t, *y = t2; int n = strlen(s) + 1; 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; 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) y[p ++] = sa[i] - k; //x[]的内容就是对应的第一关键字排名 //根据x[]的内容和y[]的下标进行合并,得到新的排名作为sa[]的下标 for(int i = 0; i < m; i ++) c[i] = 0; for(int i = 0; i < n; i ++) c[x[y[i]]] ++; for(int i = 0; i < m; i ++) c[i] += c[i - 1]; for(int i = n - 1; i >= 0; i --) sa[-- c[x[y[i]]]] = y[i]; //按照sa[]的顺序提取出老的x[],计算新的x[] swap(x, y); p = 1; x[sa[0]] = 0; for(int i = 1; i < n; i ++) { if(y[sa[i - 1]] == y[sa[i]] && y[sa[i - 1] + k] == y[sa[i] + k]) x[sa[i]] = p - 1; else x[sa[i]] = p ++; } if(p >= n) break; // 每个后缀的名次已经完全不同,不需要继续倍增 m = p; // 更新名次的最大值。 } for(int i = 1; i < n; i ++) printf("%d ", sa[i] + 1); printf("\n"); return;}inline void getHeight(){ int k = 0; int n = strlen(s) + 1; for(int i = 0; i < n; i ++) Rank[sa[i]] = i; for(int i = 0; i < n; i ++) { if(k) k --; // 从 k - 1 开始推 if(!Rank[i]) continue; int j = sa[Rank[i] - 1]; while(s[i + k] == s[j + k] && i + k < n && j + k < n) k ++; height[Rank[i]] = k; } for(int i = 2; i < n; i ++) printf("%d ", height[i]); printf("\n");}int main(){ scanf("%s", s); build_sa(300); getHeight(); return 0;}
例:abaab
那么这样我们就构造出了后缀数组。但本题还有一问,着我们要怎么解决呢?就需要两个辅助数组:rank[],height[]。rank[i] 用来记录后缀 i 在 SA 数组的位置。height[i] 记录后缀 SA[i] 和 SA[i - 1] 的 LCP 的长度。
首先很容易求出 rank 数组:
for (int i = 0; i < len; i++) rank[SA[i]] = i;
如何计算 height 数组呢?最简单的办法,相邻的两个后缀硬求一遍 LCP 时间复杂度 ,有一种更加高效的方法,只需要 时间即可。先令一个辅助数组 h,其中 h[i] = height[rank[j]]。这里有一个神奇的性质:.先来证明一下吧。
设排在后缀(i - 1)前一个的是后缀 k 。后缀(i - 1)和后缀 k 分别删除首字母后得到的是后缀 i 和后缀(k + 1)。因为后缀 k 排在 后缀(i - 1)之前,所以后缀(k + 1) 必定也排在后缀 i 之前,并且它们的 LCP 长度为h[i - 1] + 1。很显然,h[i - 1] - 1 是一系列 h 的最小值。包括排在后缀 i 之前的一个后缀 p 和 后缀 i 的 LCP 长度,即 h[i]。给出代码:
for (int i = 0; i < len; i++) { if (rank[i] == 0) {height[0] = 0; continue;} // 第一个后缀的 LCP 为 0。 if (k) k--; // 从 k - 1 开始推 int j = SA[rank[i] - 1]; while (s[i + k] == s[j + k] && i + k < len && j + k < len) k++; height[rank[i]] = k;}
0 0
- uoj 35 后缀数组first blood
- 【UOJ 35】 后缀排序|后缀数组
- 【UOJ 35】 后缀排序|后缀数组 *2
- 【UOJ 35】 后缀排序|后缀数组 *3
- UOJ #35 后缀排序(后缀数组)
- [省选前题目整理][UOJ 35]后缀排序(后缀数组)
- uoj#35: 后缀排序
- 【uoj #35】后缀排序
- 【uoj 35】后缀排序
- uoj#35. 后缀排序 后缀数组
- Uoj #35. 后缀排序(后缀数组)
- First blood...
- First blood...
- First Blood
- first blood
- first blood
- first blood
- first blood!
- 三次握手和四次挥手
- hautoj 1260
- Spark写入Parquet,暴露JDBC引擎,实现准实时SQL查询
- executeUpdate()数据库更新字段卡主问题
- (二)进制、数据类型
- uoj 35 后缀数组first blood
- 自勉
- scikit-learn的基本用法(五)——交叉验证1
- js中的innerHTML,innerText,value的区别
- C代码中向logcat输出内容
- 海康IPC+NVR+路由器+ffmpeg+nginx实现网页/Android/IOS的HLS直播
- HTML、CSS知识点总结,浅显易懂。
- jquery
- 常见的反爬虫和应对方法