zoukankan      html  css  js  c++  java
  • 后缀数组笔记

    进口后缀数组笔记

    为了简化思维,我们直接上定义。

    注释 : 本文中所有“位置”指原串位置,“排名”指每个后缀进行排序后的排名。

    (sa[i];;;) 排名为i的后缀的位置

    (rk[i];;;) 位置为i的后缀的排名

    (LCP(i,j);;;) 位置为(sa[i])(sa[j])的后缀的(LCP)

    (LCP; Lemma) 对于(forall i leq j le k , LCP(i,k) leq LCP(j,k)),排名相隔较远的肯定没有较近的有更多的前缀相同字符

    (LCP; Theorem) (LCP(i,j)=min { LCP_{k=i+1}^{j}(k-1,k) })两个排名为(i,j)的字符串LCP为他们之间的相邻LCP取最小值

    (LCP; Corollary)(ileq j < k, LCP(i,k)leq LCP(j,k))

    (height[i]) (LCP(i-1,i))(height[1]=0),即排名为(i-1)与排名为(i)的后缀的(LCP)大小

    (h[i]) (height[rk[i]])位置为(i)的后缀同它排名上前一个串的(LCP)大小


    性质:对于(i>1)(rk[i]>1),一定有(h[i]geq h[i-1]-1)

    前置:若(LCP(rk[i],rk[j])>1),当且仅当(Leftrightarrow LCP(str[i,n],str[j,n]))

    给个代码注释:

    const int S=200003;
    int T,n,c[S*2],sa[S*2],x[S*2],y[S*2],h[S*2],rk[S*2],res,m;//不开两倍串长的话第二关键字会越界访问
    char a[S];
    void get_sa()
    {
    	memset(c,0,sizeof(c));
        //这一部分是预处理。讲白了,就是第二关键字均为空串的第一二关键字的混合排序。
    	for (int i=1;i<=n;i++)
    		c[x[i]=a[i]]++;//首先将每个位置加入桶
    	for (int i=1;i<=m;i++)
    		c[i]+=c[i-1];//求前缀和,目的是得到每个值(这时候可以认为是第一关键字)的排名
    	for (int i=n;i>=1;i--)
    		sa[c[x[i]]--]=i;//倒着做,这样相同字符,原序列较后的排名较后。除了x中的字符,其他的桶值显然不必管它。显然不可以正着做。
    	for (int o=1;o<=n;o<<=1)//倍增第一关键字的长度,第二关键字长度和第一关键字相等。
    	{
            //根据基数排序的原理,我们先排第二关键字,再排第一关键字。
    		int top=0;
    		for (int i=n-o+1;i<=n;i++)//枚举后面这些第二关键字空串的一字位置。
    			y[++top]=i;//第二关键字显然是后面的空串们最靠前。
    		for (int i=1;i<=n;i++)//枚举非空串第二关键字的开头排名,按照上一次的第一关键字排完序的答案就行。
    			if (sa[i]>o)//由于前o个已经确定了是o个空串,所以非空串(长度不一定满o)不可以是原串中前o个字符,他们无法构成第二关键字。
    				y[++top]=sa[i]-o;
    		for (int i=1;i<=m;i++)
    			c[i]=0;
    		for (int i=1;i<=n;i++)
    			++c[x[i]];
    		for (int i=1;i<=m;i++)
    			c[i]+=c[i-1];
    		for (int i=n;i>=1;i--)
    			sa[c[x[y[i]]]--]=y[i];//原来的i替换为了y[i],因为原来都是空串,所以序是原串序。一样从后到前枚举。
    		for (int i=1;i<=n;i++)
    			y[i]=x[i];//就是拷贝一下数组,也可以从重新开一个数组
    		x[sa[1]]=1;top=1;//重新把排名为1的位置的值改为1
    		for (int i=1;i<=n;i++)
    			x[sa[i]]=(y[sa[i]]!=y[sa[i-1]] || y[sa[i]+o]!=y[sa[i-1]+o])+x[sa[i-1]];//然后算每一位的新值。y为基数排序结束后的老的x,x为下一轮要的新的x。
    		m=x[sa[n]];
    		if (m==n) break;//本来后缀排序后就是不会有并列的。这两句表明,如果这一轮搞定了排名,那么不必再往下比了。
    	}
    }
    void get_height(char a[],int height[],int rk[],int sa[])
    {
    	int k=0;
    	for (int i=1;i<=n;++i) rk[sa[i]]=i;//根据定义搞出rk
    	for (int i=1;i<=n;++i)
    	{
    		if (rk[i]==1) continue;根据定义啦
    		if (k) --k;//k代表h[i-1],height[rk[i-1]],我们有h[i]>=h[i-1]-1
    		int j=sa[rk[i]-1];//为了搞出这个h[i],我们找到排名上一个的位置
    		while (i+k<=n && j+k<=n && a[i+k]==a[j+k]) ++k;//然后暴力找。根据h[i]>=h[i-1]-1,总时间复杂度O(N)。
    		height[rk[i]]=k;
    	}
    }
    
  • 相关阅读:
    罗马数字转整数-算法练习
    无重复字符的最长字串-算法练习
    亲密字符串-算法刷题笔记
    0-n-1中缺失的数字-算法总结笔记
    高度检查表-算法笔记总结
    链表删除中间结点-算法练习总结
    链表中的中间结点-算法练习总结
    链表相交-算法练习总结
    多数元素-算法笔记总结
    Consul Windows集群搭建ACL Token验证
  • 原文地址:https://www.cnblogs.com/Algebra-hy/p/13082712.html
Copyright © 2011-2022 走看看