【集训第五天·后缀数组】哇哈哈哈哈~
前面放了几天假(虽然我们是在补课),所以懒得写博客。补课期间学习了网络流,后面写专题总结。网络流主要是建模,慢慢搞。
后缀数组这个东西非常难搞,当初老师讲的时候 我(lao)们(shi) 水了2h,讲完还是一脸懵逼。虽然思路是理解了,但是完全不知道代码是什么意思。cnm
然后某一天晚上我没交手机,坐在寝室的床上从23点啃代码啃到00:30,终于是明白了。。cnm
----------------------------------------------------------------------------分割线
少年啊,结束你那无聊的吐槽,进入正题吧!
好,说正事。
计算和建立后缀数组的思路大致是这样的(倍增法):
PS:排序时,如果关键字均相同,那么先出现的排在前面,即按照后缀开头字母在原串中的先后顺序排列。这样做的原因不明,我自己推测是可以后出现的排前面,但有2处这样的排序,必须保持一致
1.把每位字符单个排序,排出第一次sa数组(基数排序,代码有点。。不想吐槽)
2.枚举要计算的后缀长度,for(int k=1;k<=n;k<<1)此处就是倍增,先按照第二关键字排序(利用上一次求得的sa数组),再按照第一关键字排序(基数排序,桶桶桶),排出当前k长度下的sa数组
这样就完了,是不是很简单??(智障)
值得注意的是,代码中有两个优化
1.当所有位置的排名都不同时,即至少有n个不同排名时,break掉循环。因为再往后找也没有什么卵子用啊,第一关键字才是排序重点啊,喂
2.每次重新计算x数组,即元素数组(应该可以叫这个名字吧。。),表示不同的元素在前一次的排名(没啥卵用,只用知道它把相同的元素给揉到一块就行),这样可以优化m,即数组中元素个数
对于思路的详细图解及正确性参考
http://blog.csdn.net/yxuanwkeith/article/details/50636898
此处附上代码(网上的详解版,注释超级多)
CODE:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 void da(int *r,int *sa,int n,int m) 2 { 3 int i,k,p,*x=wa,*y=wb; //x数组相当于rank,y数组相当于第二关键字 4 for(i=0;i<m;i++) ws[i]=0; //m是字符的最大值,ws数组用于辅助基数排序 5 for(i=0;i<n;i++) ws[x[i]=r[i]]++; //计算ws和x,x只用作比较排序,所以没有必要算出真实名次 6 for(i=1;i<m;i++) ws[i]+=ws[i-1]; //计算ws 7 for(i=n-1;i>=0;i--) sa[--ws[x[i]]]=i; 8 //计算第一次的sa值 0 2 1 3 9 10 for(k=1;k<=n;k<<=1) //倍增,k是当前串的长度 11 { 12 p=0; 13 //对第二关键字进行排序,直接利用上一次计算出的sa数组 14 for(i=n-k;i<n;i++) y[p++]=i; //空串肯定小,所以用的序表示sa[] 15 for(i=0;i<n;i++) if(sa[i]>=k) y[p++]=sa[i]-k; 16 //上一次的sa左移k位还未消失,即sa[i]>=k,则按顺序写入y,得到第二关键字顺序 3 1 0 2 17 18 //对第一关键字进行排序 19 for(i=0;i<m;i++) ws[i]=0; 20 for(i=0;i<n;i++) ws[x[y[i]]]++; 21 for(i=1;i<m;i++) ws[i]+=ws[i-1]; 22 for(i=n-1;i>=0;i--) sa[--ws[x[y[i]]]]=y[i]; //0 2 3 1 23 24 //因为下一次要用rank,所以必须要计算rank的值,最终存储在x中,y此时没用了,用来临时存储rank 25 swap(x,y);//x,y为指针,所以赋值x的值到y,可直接交换指针的值 26 27 //计算新的rank,因为可能有相同串,所以对于相同的rank必须相同, 28 //因为sa已经求出,则接利用求出的sa值来求,只需要找到哪些串相等,令其rank相同,不同的依次加1 29 //方法是直接判断rank[i]和rank[i+k]的是否相同 30 p=1,x[sa[0]]=0; 31 for(i=1;i<n;i++) 32 x[sa[i]]= y[sa[i-1]==y[sa[i]]] && y[sa[i-1]+k]==y[sa[i]+k] ? p-1:p++; 33 if(p>=n)break;//优化,如果名次全部不同,则完成 34 m=p; //优化,字符最大为p,所以令m=p 35 } 36 }
应用:LCP
然而在具体应用中(寻找LCP),用的是height数组,这东西有点绕,仔细理一下还是可以想清楚
这里有一篇博客,关于这点写的非常清楚
http://www.cnblogs.com/LLGemini/p/4771235.html
有个很重要的东西就是h数组和height数组的定义,即h[i]=height[rank[i]],牢记这一点,因为height[i]不好直接计算,我们计算h[i]并通过上式求得height数组。
为了快速求得h数组(height数组)我们引入一个定理 h[i]>=h[i-1]-1,证明略
有了这个定理,我们便可在O(n)的时间内计算出 h数组(height数组),因为每次计算新的h[i]时,用之前的h[i-1]-1,再看看后面是否有更多的重复即可,非常屌,搞得zj欲罢不能
当然,今天除了看这个东西之外,我还刷了一些网络流的题,也在后面专题一起写吧。
还有哦,后缀数组13个具体套路我还没有看,等我觉得把网络流学得差不多了就看吧。
心情:过几天就要出去省选了,还有点小激动,又可以出去嗨一下了,也不知道我什么lgh最近不理我和zj两个人,到时后我们仨住三人间肯定会出现一些尴尬的情况,也只能随机应变了。大概可以让lgh和lence_ren睡双人间,我、zj、zbh一起浪。。。
If you live in the echo,
your heart never beats as loud.
如果你生活在回声里,
你的心跳声永远不会轰鸣作响。