[Luogu3809] 【模板】后缀排序 [SA]

Link
Luogu - https://www.luogu.org/problemnew/show/P3809


后缀排序是后缀数组的基本功能:将一个长度为 n n n 的字符串所有的后缀在 O ( n ) O(n) O(n) 时间内按照字典序排序。

涉及到一个基数排序……然后我发现很多人都一知半解啊?
SA[Tax[Rk[Tp[i]]]--] = Tp[i]; 这一句是个啥?
逐个分析的话就是,
找到第二关键字排名为 i 的那个后缀 Tp[i]
找到 Tp[i] 的第一关键字的排名 Rk[…]
接着,把所有后缀按照第一关键字+第二关键字排序后,找到第一关键字相同的所有后缀里面,
排最后的那个后缀在所有后缀里的排名 Tax[…]
……然后把这个排名对应的后缀设置为 Tp[i]
接着,把这个排名减一 ???????????????
——实际上,在求前缀和之后, Tax[i] 的意义是:第一关键字排名不大于 i \pmb i iii 的后缀的数量
并且这个意义仅在枚举对应的第一关键字的时候有效!!!
如果你要让它全程保持一个很讲得通的意义的话复杂度就退化啦。
另外,基数排序之前 Tp[] 的意义是第二关键字排第 i i i 小的后缀的位置;
基数排序之后 Tp[] 的意义没有了,不过这个时候 SA[] 是按照第一+第二关键字排序完毕的。
这时候 Rk[] 还没有跟上所以最后要加一步处理。

至于第一关键字和第二关键字是什么呀?
后缀数组的倍增挺巧妙的,用心去感受(x
实际上就是这样嘛。比如说
BANANA$
第一个后缀
BANANA$
它第一次的两个关键字
B A
第二次
BA NA
第三次
BANA NA$
就这么个意思。

排序的时候如
B A N A N A $
2 1 3 1 3 1 4
BA AN NA AN NA A$ $
21 13 31 13 31 14 40
3 1 4 1 4 2 5
BANA ANAN NANA ANA$ NA$ A$ $
34 11 44 12 45 20 50
4 1 5 2 6 3 7
实际上,在这一步就已经排序完成了。
如果没有的话,那还要继续,就变成
BANANA$ ANANA$ NANA$ ANA$ NA$ A$ $
然后一定会结束。


#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cctype>
#include<cmath>
#include<ctime>
#include<iostream>
#include<cstring>
using namespace std;
#define R register
const int MAXN = 2e6 + 5;
string S;
int n, m, SA[MAXN], Rk[MAXN], Tp[MAXN], Tax[MAXN];
void Radix_Sort()
{
	fill(Tax, Tax + 1 + m, 0);
	for (R int i = 1; i <= n; ++i) ++Tax[Rk[i]];
	for (R int i = 1; i <= m; ++i) Tax[i] += Tax[i - 1];
	for (R int i = n; i >= 1; --i) SA[Tax[Rk[Tp[i]]]--] = Tp[i];
}
void Suffix_Sort()
{ 
    m = 127;
	for (R int i = 1; i <= n; ++i) Rk[i] = S[i], Tp[i] = i; // initialize
	Radix_Sort(); // First Round Suffix_Sort
	
	for (R int Len = 1, tot = 0; tot < n; m = tot, Len <<= 1)
	{
		// Initialize Tp[]
		tot = 0;
		for (R int i = n - Len + 1; i <= n; ++i) Tp[++tot] = i; // They DONT have second keywords
		for (R int i = 1; i <= n; ++i)
		{
			if (SA[i] > Len) 
			{
				// So it could be a second keyword ...
				Tp[++tot] = SA[i] - Len; // SA[i] is the keyword of (SA[i] - Len)
			}
		}
		
		Radix_Sort(); // Next Round Suffix_Sort
		// Tp[] is useless now.
		
		swap(Rk, Tp); // Copy Rk[] to Tp[]
		// And then We'll update Rk[]
		
		// Descretization
		Rk[SA[1]] = tot = 1;
		for (R int i = 2; i <= n; ++i) 
		{
			if (!(Tp[SA[i]] == Tp[SA[i - 1]] && Tp[SA[i] + Len] == Tp[SA[i - 1] + Len])) ++tot;
			Rk[SA[i]] = tot;
		}
		
	}
}
int main()
{
	ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
	cin >> S;
	n = S.size();
	for (R int i = n; i >= 1; --i) S[i] = S[i-1];
	Suffix_Sort();
	for (R int i = 1; i <= n; ++i) cout << SA[i] << " ";
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值