【luogu P3809】【模板】后缀排序

【模板】后缀排序

题目链接:luogu P3809

题目大意

给你一个字符串,要你输出它每个后缀的字典序排序。

思路

这道题是 SA 的模板题。

相对别的比较好打。
(然后我打了一整天)

它主要是用基数排序的思想排序,再用倍增加以优化。

那怎么倍增呢?
你假设你已经处理了长度为 \(k\) 的,然后你要处理 \(k\times 2\) 的。
然后你知道了对于你要求的长度是 \(k\times 2\) 的,你是先按前面的 \(k\) 的排序,然后如果相同,再看后面 \(k\) 的排序。
那我们就可以知道如果前面的排序相同,那它组成新的排序一定是在连续的一段。

那我们就对于每一段,倒叙枚举后面的部分,然后从区间从后面开始放。
那如果它到没有后面部分的时候呢?
那就直接设后面的排名是 \(0\),让它放在最前面。

那按这个方法,从 \(1\) 长度开始处理,你就可以得到最后要的 \(sa_i\) 数组了。

代码

#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;

int n, m;
int p;
int sa[1000001], rak[1000001], tp[1000001], tax[1000001];
char c[1000001];

void paixu() {//基数排序
	for (int i = 1; i <= m; i++)
		tax[i] = 0;
	for (int i = 1; i <= n; i++)
		tax[rak[i]]++;
	for (int i = 2; i <= m; i++)
		tax[i] += tax[i - 1];
	for (int i = n; i >= 1; i--)
		sa[tax[rak[tp[i]]]--] = tp[i];	
}

int main() {
	scanf("%s", c + 1);
	n = strlen(c + 1);
	
	for (int i = 1; i <= n; i++) {
		rak[i] = c[i];
		tp[i] = i;
	}
	
	m = 122;
	paixu();
	
	for (int w = 1; w < n; w <<= 1) {//SA
		m = p;
		
		p = 0;
		
		for (int i = n - w + 1; i <= n; i++)
			tp[++p] = i;
		for (int i = 1; i <= n; i++)
			if (sa[i] > w) tp[++p] = sa[i] - w;
		
		paixu();
		
		swap(tp, rak);
		
		p = 1;
		rak[sa[1]] = 1;
		for (int i = 2; i <= n; i++)
			if (tp[sa[i]] == tp[sa[i - 1]] && tp[sa[i] + w] == tp[sa[i - 1] + w])
				rak[sa[i]] = p;
			else rak[sa[i]] = ++p;
		
		if (p == n) break;
	}
	
	for (int i = 1; i <= n; i++)
		printf("%d ", sa[i]);
	
	return 0;
}
posted @ 2021-02-22 15:32  あおいSakura  阅读(59)  评论(0编辑  收藏  举报