Battle of the Brains
后缀数组
今天拜读了罗穗骞的神作《后缀数组——处理字符串的有力工具》
这篇主要是模板以及应用,还有一篇许智磊的《后缀数组》主要讲概念和证明
两者结合疗效好- -
刚刚开始做的时候觉得不过尔尔,模板题而已
但越做到后边PKU3693越发觉有趣,非常巧妙的统计,随便把RMQ也学了
跟着论文做了几道题目,但还不知道以后出现后缀数组的题能不能解出来,呵呵
刷题途中发现3xian总是排第一—-无限崇拜并馋涎其模板,发现他的最大流,(kth number)图灵树,后缀树模板极其的高效
献上模板,倍增算法nlog(n),基本上时仿照论文里写的,加了点常数优化
另外一个DC3编程复杂度大,并且效率只是提高一点点,不是严格意义上的线性算法,没写(其实是我偷懒- -)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 | //rank从0开始 //sa从1开始,因为最后一个字符(最小的)排在第0位 //high从2开始,因为表示的是sa[i-1]和sa[i] #define M 220000 int rank[M],sa[M],X[M],Y[M],high[M],init[M]; int buc[M]; void calhigh(int n) { int i , j , k = 0; for(i = 1 ; i <= n ; i++) rank[sa[i]] = i; for(i = 0 ; i < n ; high[rank[i++]] = k) for(k?k--:0 , j = sa[rank[i]-1] ; init[i+k] == init[j+k] ; k++); } bool cmp(int *r,int a,int b,int l) { return (r[a] == r[b] && r[a+l] == r[b+l]); } void suffix(int n,int m = 128) { int i , l , p , *x = X , *y = Y; for(i = 0 ; i < m ; i ++) buc[i] = 0; for(i = 0 ; i < n ; i ++) buc[ x[i] = init[i] ] ++; for(i = 1 ; i < m ; i ++) buc[i] += buc[i-1]; for(i = n - 1; i >= 0 ; i --) sa[ --buc[ x[i] ]] = i; for(l = 1,p = 1 ; p < n ; m = p , l *= 2) { p = 0; for(i = n-l ; i < n ; i ++) y[p++] = i; for(i = 0 ; i < n ; i ++) if(sa[i] >= l) y[p++] = sa[i] - l; for(i = 0 ; i < m ; i ++) buc[i] = 0; for(i = 0 ; i < n ; i ++) buc[ x[y[i]] ] ++; for(i = 1 ; i < m ; i ++) buc[i] += buc[i-1]; for(i = n - 1; i >= 0 ; i --) sa[ --buc[ x[y[i]] ] ] = y[i]; for(swap(x,y) , x[sa[0]] = 0 , i = 1 , p = 1 ; i < n ; i ++) x[ sa[i] ] = cmp(y,sa[i-1],sa[i],l) ? p-1 : p++; } calhigh(n-1);//后缀数组关键是求出high,所以求sa的时候顺便把rank和high求出来 } //当需要反复询问两个后缀的最长公共前缀时用到RMQ int Log[M]; int best[20][M]; void initRMQ(int n) {//初始化RMQ for(int i = 1; i <= n ; i ++) best[0][i] = high[i]; for(int i = 1; i <= Log[n] ; i ++) { int limit = n - (1<<i) + 1; for(int j = 1; j <= limit ; j ++) { best[i][j] = min(best[i-1][j] , best[i-1][j+(1<<i>>1)]); } } } int lcp(int a,int b) {//询问a,b后缀的最长公共前缀 a = rank[a]; b = rank[b]; if(a > b) swap(a,b); a ++; int t = Log[b - a + 1]; return min(best[t][a] , best[t][b - (1<<t) + 1]); } int main() { //预处理每个数字的Log值,常数优化,用于RMQ Log[0] = -1; for(int i = 1; i <= M ; i ++) { Log[i] = (i&(i-1)) ? Log[i-1] : Log[i-1] + 1 ; } //******************************************* // n为数组长度,下标0开始 // 将初始数据,保存在init里,并且保证每个数字都比0大 // m = max{ init[i] } + 1 // 一般情况下大多是字符操作,所以128足够了 //******************************************* init[n] = 0; suffix(n+1,m); initRMQ(n); } |
下边收入几道论文上没提的后缀数组题目
zoj3199 Longest Repeated Substring
连续重复至少1次的最长串
spoj1811 Longest Common Substring
普通的LCS,同PKU2774,不过数据量超大,上边的模板超时,DC3刚好卡过,据说正解是后缀自动机,Orz
hdu2890 Longest Repeated subsequence
不可重叠k次最长重复子串.分层后排序贪心nlog(n)
Popularity: 7% [?]
| 打印文章 | 这篇文章由shǎ崽于2009/12/15 17:52发表在Algorithm。你可以订阅RSS 2.0 也可以发表评论或引用到你的网站。 |
大约5月前
求图灵树资料……google不到……
[回复]
shǎ崽 回复:
三月 22nd, 2010 at 20:54
必须用3xian搜索引擎
[回复]
TonyShaw 回复:
三月 28th, 2010 at 09:37
那是什么?
[回复]
匿名 回复:
三月 30th, 2010 at 20:41
3xian 是gdut的大牛
[回复]
shǎ崽 回复:
四月 1st, 2010 at 10:17
算是一类很繁琐的模板题吧
[回复]
大约8月前
PKU2777是线段树吧
[回复]
shǎ崽 回复:
一月 4th, 2010 at 19:36
恩,手滑了,2777是线段树.2774才是后缀树
[回复]
大约8月前
DC3不是严格意义上的linear algorithm…
这个要求有点太高了吧,基于比较的linear algorithm是不存在的,可以用排序规约,下界必然是O(nlgn)
算法复杂度加上个字母表大小不过分吧,绝大部分字符串字母表大小也就是128左右
[回复]
shǎ崽 回复:
一月 3rd, 2010 at 17:43
不愧是教主,什么算法都研究的这么透彻,其实我偷懒还没怎么研究过DC3.
[回复]