zoukankan      html  css  js  c++  java
  • 后缀数组学习小计

    其实很久之前思想上就会了,只不过一直没有打过Orz,这两天打了一下,其实比SAM好理解多了呀

    后缀数组的定义

    • 将字符串s以所有位置开头的后缀排个序。
    • SA[i]:排名第i的后缀的开头。
    • Rank[i]:以i开头的后缀的排名。
    • SA与Rank其实是互逆的。

    后缀数组的求法

    • 有一种比较快的DC3可以做到O(n),但是常数巨大,不推荐使用(除非出题人真的卡你)
    • 所以还是老老实实用倍增吧,O(n log n),常数比较小。
    • 大体方法就是得到每个位置开头长度k的,再通过基数排序(双关键字桶排)得到长度为k*2的。最终得到长度为n的(多的空字符视作最小的字符)。
    int sa[maxn],rk[maxn],x[maxn],d[maxn],t[maxn],h[maxn];
    int cmp(int i,int j,int k){return t[i]==t[j]&&t[i+k]==t[j+k];}
    void GetSA(){
    	m=255;
    	for(i=1;i<=n;i++) t[x[i]=s[i]]++;
    	for(i=0;i<m;i++) t[i]+=t[i-1];
    	for(i=n;i>=1;i--) sa[t[x[i]]--]=i;
    	
    	for(k=1;k<=n;k<<=1){
    		int p=0;
    		for(i=n-k+1;i<=n;i++) d[++p]=i;
    		for(i=1;i<=n;i++) if (sa[i]>k) d[++p]=sa[i]-k;
    		for(i=0;i<=m;i++) t[i]=0;
    		for(i=1;i<=n;i++) t[x[i]]++;
    		for(i=1;i<=m;i++) t[i]+=t[i-1];
    		for(i=n;i>=1;i--) sa[t[x[d[i]]]--]=d[i];
    		memcpy(t,x,sizeof(x));
    		x[sa[1]]=p=1; 
    		for(i=2;i<=n;i++) x[sa[i]]=cmp(sa[i],sa[i-1],k)?p:++p;
    		if (p>=n) break; m=p;
    	}
    	for(i=1;i<=n;i++) rk[sa[i]]=i;
    	for(i=1,j=0;i<=n;i++) {
    		if (j) j--;
    		while (s[i+j]==s[sa[rk[i]-1]+j]) j++;
    		h[rk[i]]=j;
    	}
    }
    
    • 实现的时候先将长度为1的预处理好。
    • 排序的时候记录x[i]表示以i开头的长度为k的子串在所有子串中的大小,也就是rank,可以有两个x相等。
    • 我们将所有的第一关键字丢到一个桶里面计数,然后按照第二关键字从大到小枚举,对于这个位置的第一关键字,如果与其他位置的第一关键字相等,但是因为第二关键字的缘故,编号自然是在这个第一关键字对应的区间中从后往前选的。
    • 所以我们需要得到第二关键字排序的顺序,首先对于i=[n-k+1,n],它们的第二关键字为空,先加进队列d里。
    • 然后接下来的顺序就可以直接根据SA(因为之前已经排好了)逐个加进去了。这样就可以得到顺序了。
    • 然后我们还需要得到新的x。同样按照新的SA的顺序去枚举,如果当前的排序与前一个不一样的话才cnt++。
    • 实际上最后的x就是rank

    后缀数组的应用

    • 后缀数组有一个很重要的东西height[i],表示suffix(SA[i])与suffix(SA[i-1])的最长公共前缀。
    • 对于求这个height,我们有一个结论
    • height[rank[i]]>=height[rank[i1]]1height[rank[i]]>=height[rank[i-1]]-1
    • 证明如下(其实随便画画图就可以得到)

    设k=SA[rank[i-1]-1],height[rank[i-1]]为suffix(k)与suffix(i-1)的最长公共前缀
    如果去掉这两个字符串的开头的话,得到的就是suffix(k+1) 和 suffix(i),显然suffix(k+1) 在suffix(i)的前面。
    那么在i前的一定在suffix(k+1)和suffix(i)之间。所以这两个的前缀至少是height[rank[i-1]]-1。

    • 所以我们只需要根据rank去求就可以了。

    常规操作

    • 求任意两个后缀i,j的最长公共前缀长度:height[rank[i]+1…rank[j]]的最小值
    • 还有很多神奇的应用,但是我接触的不多
  • 相关阅读:
    <C> 链表 双向链表 栈 队列
    <C> 结构体
    <C> getchar()函数 如何把getchar()到的字符串存起来的实际应用
    DataSet转换为泛型集合和DataRow 转成 模型类
    对DataSet,DataRow,DateTable转换成相应的模型
    Json对象与Json字符串互转(4种转换方式)
    Android开发 使用HBuilder的缓存方法
    MIT_AI公开课p1p2学习笔记
    LeetCode_02 两数相加【链表】
    leetcode_01两数之和
  • 原文地址:https://www.cnblogs.com/DeepThinking/p/13090941.html
Copyright © 2011-2022 走看看