poj2774 后缀数组
2016-02-19 22:46
239 查看
/* poj2774 题意: 给两个字符串(长度不超过100000),求最长公共子串 用我学过的平常的暴力或kmp的方法都会超时,所以刚好可以用来 当做后缀数组的入门题 在下边借用一些论文里的知识再加上自己的一些理解 一、后缀数组的实现 后缀:就是从某个位置i开始到整个字符串末尾的特殊的字符串,用suffix(i)表示 大小比较:关于字符串的大小比较,是指通常所说的“字典顺序”比较,也 就是对于两个字符串u、v,令i 从1 开始顺次比较u[i]和v[i],如果 u[i]=v[i]则令i 加1,否则若u[i]<v[i]则认为u<v,u[i]>v[i]则认为u>v (也就是v<u),比较结束。如果i>len(u)或者i>len(v)仍比较不出结果,那 么若len(u)<len(v) 则认为u<v , 若len(u)=len(v) 则认为u=v , 若 len(u)>len(v)则u>v。 后缀数组:后缀数组SA 是一个一维数组,它保存1..n 的某个排列SA[1], SA[2],……,SA ,并且保证Suffix(SA[i]) < Suffix(SA[i+1]),1≤i<n。 也就是将S 的n 个后缀从小到大进行排序之后把排好序的后缀的开头位置顺 次放入SA 中。 名次数组:名次数组Rank[i]保存的是Suffix(i)在所有后缀中从小到大排 列的“名次”。 简单的说,后缀数组是“排第几的是谁?”,名次数组是“你排第几?”。 求后缀数组与名次数组的方法 有2路倍增算法和3DC3算法两种,第一种方法,容易实现容易理解,但是没第二种方法更高效 height 数组:定义height[i]=suffix(sa[i-1])和suffix(sa[i])的最长公 共前缀,也就是排名相邻的两个后缀的最长公共前缀。 定义h[i]=height[rank[i]],也就是suffix(i)和在它前一名的后缀的最长公共前缀。 h 数组有以下性质:h[i]≥h[i-1]-1 证明省略,感兴趣可以看论文,所以按照 h[1],h[2]...h 的顺序求height[]会是时间上得到优化 后缀数组的好多应用都和sa[],height[],rank[]有关 对本题求最长公共子串,即可把两个字符串合并,求height得最大值 是要考虑公共前缀必须来自不同的字符串,所以要在中间加上一个特殊字符, 并且判断是否来自不同的字符串 */ #include <iostream> #include <cstdio> #include <cstring> #include <vector> #include <cmath> #include <algorithm> typedef long long ll; using namespace std; const int maxn = 200000 + 5; int wa[maxn], wb[maxn], wv[maxn], wsf[maxn]; int sa[maxn], Rank[maxn], height[maxn], s[maxn]; char str1[maxn], str2[maxn]; int cmp(int *r, int a, int b, int l) { return r[a] == r[b] && r[a+l] == r[b+l]; } void getsa(int *r, int *sa, int n, int m)//在字符串后边加上0,让其代表的后缀排名为0 { //有比较时不越界的用处 int *x = wa, *y = wb, *t; /*二路倍增算法是对每个字符开始的长度为2的k次方的子字符串进行排序, 求出排名,就是rank值,当2的k次方大于n时就对所有的后缀排好了序 每一次排序都可以利用上次排好的2的k-1次方的结论来优化,那么长为 2的k次方的字符串就可以用两个2的k-1次方的rank值来表示,对两位从低到高 进行基数排序即可 */ //首先对长为1的字符串进行基数排序,wsf存储的是相当于前缀和 //x[]数组相当于临时的rank[]数组 for (int i = 0; i < m; i++) wsf[i] = 0;//每次分配前清空计数器 for (int i = 0; i < n; i++) wsf[x[i]=r[i]]++;//统计每个桶中的记录数 for (int i = 1; i < m; i++) wsf[i] += wsf[i-1];//将x[]中的位置依次分配给每个桶 for (int i = n-1; i >= 0; i--) sa[--wsf[x[i]]] = i;//将所有桶中记录依次收集到sa中 for (int j = 1, p = 1; p < n; j*=2, m = p) { //cout << "kkk" << j << endl; //第一次基数排序时,用到上次sa中的结果,y数组存下第一次基数排序的结果 int i = n - j; for (p = 0; i < n; i++) y[p++] = i;//后边的要补上0,所以直接是排名最前的 for (i = 0; i < n; i++) if (sa[i] >= j) y[p++] = sa[i]-j; for (i = 0; i < n; i++) wv[i] = x[y[i]];//第二次基数排序 for (i = 0; i < m; i++) wsf[i] = 0; for (i = 0; i < n; i++) wsf[wv[i]]++; for (i = 1; i < m; i++) wsf[i] += wsf[i-1]; for (i = n-1; i >= 0; i--) sa[--wsf[wv[i]]] = y[i]; //x和y数组进行交换,y存下第二次排序后的结果 //然后求得临时的rank[]数组,p为排的的名次,这里进行一个优化, //如果p<n说明排名有相同的则要继续进行比较 for (t = x, x = y, y = t, x[sa[0]] = 0, i = 1, p = 1; i < n; i++) { x[sa[i]] = cmp(y, sa[i-1], sa[i], j)? p-1 : p++; } } return; } void getheight(int *r, int n)//字符串后边不加0 { //h[1],h[2]...h 的顺序求height[] //这里赋值Rank[]从1开始,防止下边-1操作越界 //且求height[1]时刚好和最后的字符串后的0比较 //也是求height字符串后不加0的的巧妙之处 for (int i = 1; i <= n; i++) Rank[sa[i]] = i; int k = 0; for (int i = 0; i < n; i++) { if (k)k--; else k = 0; int j = sa[Rank[i]-1];//suffix(i)前一名的后缀字符串的起始位置 while(r[i+k] == r[j+k])k++; height[Rank[i]] = k; } } int main() { //freopen("input.txt", "r", stdin); while(scanf("%s%s", str1, str2) == 2) { //puts(str1); //puts(str2); int n = 0; int len = strlen(str1); for (int i = 0; i < len; i++) s[n++] = str1[i]-'a'+1; s[n++] = 30; len = strlen(str2); for (int i = 0; i < len; i++) s[n++] = str2[i]-'a'+1; s = 0; getsa(s, sa, n+1, 31); getheight(s, n); int ans = 0; len = strlen(str1); for (int i = 2; i <= n; i++) { if (height[i] > ans) { //判断起始位置即可 if (sa[i-1] >= 0 && ((sa[i-1] < len && sa[i] >= len) || (sa[i] < len && sa[i-1] >= len))) ans = height[i]; } } cout << ans << endl; } return 0; }
相关文章推荐
- Spark应用开发如何设定配置生效
- Unity3d热更新 从AssetBundle说起
- 算法竞赛入门经典(第二版)-刘汝佳-第四章 函数与递归 例题+习题(15/16)
- 矢量化的HTML5拓扑图形组件设计
- IDEA ant连接ftp报错NoClassDefFoundError
- 几种常用加密算法比较
- 程序员必备素质-团队
- 运算符重载之 []
- (18)[AppWidget]
- CodeForces 361D Levko and Array(二分+dp)
- 周期串
- RPi 2B apache2 mysql php5 and vsftp
- 队列 hdu1276
- 队列
- XCode调试中的输出技巧
- 基于Spark Streaming预测股票走势的例子(一)
- Label 自适应文本(StoryBoard/xib)
- 最近关于evaluation model的进阶理解(待更新)
- Session
- 2016/02/19 codes