首页 > 其他分享 >后缀数组

后缀数组

时间:2024-02-22 21:13:33浏览次数:24  
标签:后缀 tp tot 数组 sa 排序 rk

虽然这是基础算法,但我已经好几次忘记它怎么写了,反倒是 SAM 记得很牢。

为了避免比赛中因这个爆蛋,我打算仔细梳理一下它的原理。


问题:给你一个字符串,你需要求出 \(sa_i,rk_i\),其中 \(sa_i\) 表示排名为 \(i\) 的后缀,\(rk_i\) 表示后缀 \(i\) 的排名。


首先暴力就是直接快排,里面二分哈希,\(\Theta(n \log^2 n)\)。

然后考虑倍增。

每次我们按照后缀的前 \(w\) 个字符排序,然后 \(w\) 每次乘 \(2\),最后就能完成排序。

最初 \(w=1\),若串为 \(\verb!ababa!\),则排序结果如下:

接着我们令 \(w=2\),考虑求出此时每个后缀的排名。

不难发现,此时后缀的排名就是以它自己的排名为第一关键字,它后面的那个后缀为第二关键字排序后的排名,也就是 \(\verb!pair!\) \((rk_i,rk_{i+1})\) 的排名。

同理,\(w\) 的排序结果转移到 \(2 \times w\) 就是按照 \(\verb!pair!\) \((rk_i,rk_{i+w})\) 排序。

此时直接暴力排序依然是 \(\Theta(n \log^2 n)\) 的。

考虑基数排序优化,可以得到 \(\Theta(n \log n)\) 的复杂度。

代码:

inline void kun_sort(){
	  rep(i,0,m) sm[i]=0;
  	rep(i,1,n) ++sm[rk[i]];
  	rep(i,1,m) sm[i]+=sm[i-1];
  	//处理第一关键字排名,现在 sm[i] 表示的是第一关键字为 rk[i] 的最后排名
  	per(i,n,1) sa[sm[rk[tp[i]]]--]=tp[i];
  	//倒序加入
}
inline void SA(){
	  rep(i,1,n) tp[i]=i,rk[i]=s[i];
  	m=200,kun_sort();
  	int tot=0;
  	for (int w=1;tot<n;w<<=1){
  		  tot=0;
    		rep(i,n-w+1,n) tp[++tot]=i;
    		rep(i,1,n) if (sa[i]>w) tp[++tot]=sa[i]-w;
    		//按第二关键字排序
    		kun_sort(),swap(tp,rk);
    		tot=rk[sa[1]]=1;
    		rep(i,2,n)
    			  rk[sa[i]]=(tp[sa[i]]==tp[sa[i-1]] && tp[sa[i]+w]==tp[sa[i-1]+w])?tot:++tot;
    		m=tot;
  	}
}

\(\rm height\) 数组表示 \(sa_i\) 和 \(sa_{i-1}\) 的 LCP。

基于下面这个结论,我们可以快速求出 \(\rm height\) 数组:

\[height_{rk_i} \ge height_{rk_{i-1}}-1 \]

int k=0;
rep(i,1,n){
		if (k) --k;
		while (i+k<=n && sa[rk[i-1]]+k<=n && s[i+k]==s[sa[rk[i-1]]+k]) ++k;
		height[rk[i]]=k;
}

标签:后缀,tp,tot,数组,sa,排序,rk
From: https://www.cnblogs.com/tx-lcy/p/18028198

相关文章

  • 代码随想录算法训练营day02 | leetcode 977. 有序数组的平方、35.搜索插入位置、34.在
    题目链接:977.有序数组的平方-简单题目描述:给你一个按非递减顺序排序的整数数组nums,返回每个数字的平方组成的新数组,要求也按非递减顺序排序。示例1:输入:nums=[-4,-1,0,3,10]输出:[0,1,9,16,100]解释:平方后,数组变为[16,1,0,9,100]排序后,数组变为[0,1,9,16,100]......
  • 使用delete和Vue.delete删除数组元素的区别
    JavaScript中的delete运算符可以删除对象的属性,但是它不适用于数组。如果你试图使用delete运算符删除数组中的元素,你会发现该元素的值变为undefined,而数组的长度并没有改变。Vue.js提供了一个名为Vue.delete的方法,它可以帮助我们在删除数组元素时触发响应式更新。与原生JavaScrip......
  • [学习笔记]树状数组
    1.引入树状数组是一种支持单点修改和区间查询的,代码量小的数据结构。(我只看到了代码量小)什么是「单点修改」和「区间查询」?假设有这样一道题:已知一个数列a,你需要进行下面两种操作:「单点修改」:给定\(x,y\),将\(a[x]\)自增$y$。「区间查询」:给定\(l,r\),求解\(a[......
  • JavaScript 的新数组分组方法
    对数组中的项目进行分组,你可能已经做过很多次了。每次都会手动编写一个分组函数,或者使用lodash的groupBy函数。好消息是,JavaScript现在有了分组方法,所以你再也不必这样做了。Object.groupBy和Map.groupBy这两个新方法将使分组变得更简单,并节省我们的时间或依赖性。以前......
  • 代码随想录 day57 最长公共子序列 不相交的线 最大子数组和
    最长公共子序列dp[i][j]:长度为[0,i-1]的字符串text1与长度为[0,j-1]的字符串text2的最长公共子序列为dp[i][j]主要就是两大情况:text1[i-1]与text2[j-1]相同,text1[i-1]与text2[j-1]不相同如果text1[i-1]与text2[j-1]相同,那么找到了一个公共元素,所以dp......
  • # 代码随想录算法训练营day01 | leetcode 704. 二分查找、35.搜索插入位置、34.在排序
    题目链接:704.二分查找-简单题目描述:给定一个n个元素有序的(升序)整型数组nums和一个目标值target,写一个函数搜索nums中的target,如果目标值存在返回下标,否则返回-1。示例1:输入:nums=[-1,0,3,5,9,12],target=9输出:4解释:9出现在nums中并且下标为4示......
  • 数组常用方法
    foreach和之前for循环作用基本一样,只不过比for更灵活方便一些参数:回调函数,该回调函数有三个参数遍历项索引该数组indexof用于查找在数组中的位置,返回值为索引,如果没有找到返回-1参数:第一个参数为要查找的数据第二个参数为从哪个位置开始constarr=[1,2,3,4,5]......
  • 第十八节:动态规划面试题(爬楼梯、买卖股票时机、最大子数组和)
    一.        二.        三.         !作       者:Yaopengfei(姚鹏飞)博客地址:http://www.cnblogs.com/yaopengfei/声     明1:如有错误,欢迎讨论,请勿谩骂^_^。声     明2:原创博客请在转载......
  • 【C++】编写一个具有老式风格接口的函数,其原型如下:int reduce(long arr[], int n)。实
    #include<iostream>#include<string>usingnamespacestd;intreduce(longarr[],intn){sort(arr,arr+n);autostr=unique(arr,arr+n);returnstr-arr;}intmain(){longarr[10]={15,8,5,6,11,11,6,6,198,50};......
  • 合并两个有序数组
    题目描述:两个按非递减顺序排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。合并 nums2 到 nums1中,使合并后的数组同样按非递减顺序排列。注意:最终,合并后数组不应由函数返回,而是存储在数组 nums1 中。为了应对这种情况......