2019.8.5 金华正睿集训总结Day9

8.5

今天跑到C班了,表示十分快乐

上午

后缀数组

我居然有些理解了!!

感谢C班!!

后缀数组的构造

sa[i]:第i小的后缀的编号。

rank[i]:编号为i的后缀的排名(从小到大)。

height[i]:第i小的后缀和第i - 1 小的后缀的最长公共前缀,即LCP(suffix[sa[i]],suffix[sa[i-1]])

倍增,想要求出每个位置往后2k个字符,这些字符串的排名。

由长为2k的子串的排名可以求出长为2k+1的子串的排名

倍增O(logn)层,每层快速排序的话就是O(nlog2n),基数排序的话就是O(nlogn)。

任意两个后缀的最长公共前缀

设为后缀x和后缀y,其中rank[x]<rank[y]。

答案就是min(rank[x],rank[y])height[i]

如果已经求出了height,用ST表就可以O(nlogn)预处理,O(1)回答两个后缀的最长公共前缀。

借个图
在这里插入图片描述

如何求 height 数组

性质: height[rank[i]] ≥ height[rank[i − 1]] − 1

如果 height[rank[i − 1]] = 0 显然成立,否则在 i − 1 这个后缀与它前驱的 LCP 的基础上去掉第一个字符,就找到了一个后缀与 i 这个后缀的 LCP≥ height[rank[i − 1]] − 1。 进而有height[rank[i]] ≥height[rank[i − 1]] − 1。
在这里插入图片描述
申明此图来自博主

基数排序

开权值范围个桶,作个前缀和求出每个数的排名,然后直接把每个数放到该放的位置上去。

稳定排序,相等的元素就是按原顺序排的。

在这里插入图片描述
我是木得感情的截图机器人

模板题

【uoj】35 后缀排序

非常模板

code

#include <bits/stdc++.h>
#define ll long long
#define lowbit(x) x & (-x)
#define memset(x) memset(x, 0, sizeof(x))
#define N 500010
#define sc(x) scanf("%d", &x)
#define pr(x) printf("%d\n", x)
using namespace std;
char s[N];
int n, p;
int sa[N], cnt[N], rank[N], ran[N], tmp[N], h[N];
void buildSA()
{
	p = 0;
	for (int i = 1; i <= n; i++)
	p = max(p, (int)s[i]);
	for (int i = 1; i <= n; i++)
	cnt[s[i]]++;
	for (int i = 1; i <= p; i++)
	cnt[i] += cnt[i - 1];
	for (int i = n; i >= 1; i--)
	sa[cnt[s[i]]--] = i;
	rank[sa[1]] = p = 1;
	for (int i = 2; i <= n; i++)
	rank[sa[i]] = (s[sa[i]] == s[sa[i - 1]] ? p : ++p);
	for (int k = 1; k <= n; k <<= 1)
	{
		memset(cnt);
		for (int i = 1; i <= n; i++)
		cnt[rank[i + k]]++;
		for (int i = 1; i <= p; i++)
		cnt[i] += cnt[i - 1];
		for (int i = n; i >= 1; i--)
		tmp[cnt[rank[i + k]]--] = i;
		memset(cnt);
		for (int i = 1; i <= n; i++)
		cnt[rank[i]]++;
		for (int i = 1; i <= p; i++)
		cnt[i] += cnt[i - 1];
		for (int i = n; i >= 1; i--)
		sa[cnt[rank[tmp[i]]]--] = tmp[i];
		ran[sa[1]] = p = 1;
		for (int i = 2; i <= n; i++)
		ran[sa[i]] = (rank[sa[i]] == rank[sa[i - 1]] && rank[sa[i] + k] == rank[sa[i - 1] + k] ? p : ++p);
		for (int i = 1; i <= n; i++)
		rank[i] = ran[i];
	}
	for (int i = 1, j = 0; i <= n; i++)
	{
		if (rank[i] == 1) continue;
		while (s[i + j] == s[sa[rank[i] - 1] + j])
		{
			j++;	
		} 
		h[rank[i]] = j;
		if (j) j--;
	}
}
int main()
{
	cin >> s + 1;
	n = strlen(s + 1);
	buildSA(); 
	for (int i = 1; i <= n; i++)
	printf("%d ", sa[i]);
	printf("\n");
	for (int i = 2; i <= n; i++)
	printf("%d ", h[i]);
	printf("\n");
	return 0;
}

Trie 上后缀数组

这还不会

Trie 上一个后缀定义为一个点的到根路径。

求后缀数组直接类比序列情形,倍增。

如果把中间过程的 rank 数组都记录下来就可以求两个节点到根路径的 LCP 了。
在这里插入图片描述

基础应用

参考论文:罗穗骞:《后缀数组——处理字符串的有力工具》

例题(来自罗穗骞:《后缀数组——处理字符串的有力工具》)

下午

做题

A - Musical Theme

先得到差分序列,再用后缀数组找出最长公共前缀,中间利用了二分答案

code

#include<set>
#include<map>
#include<cmath>
#include<queue>
#include<bitset>
#include<string>
#include<cstdio>
#include<cctype>
#include<cassert>
#include<cstdlib>
#include<cstring>
#include<sstream>
#include<iostream>
#include<algorithm>
#define ll long long
#define lowbit(x) x & (-x)
#define memset(x) memset(x, 0, sizeof(x))
#define N 500010
#define sc(x) scanf("%d", &x)
#define pr(x) printf("%d\n", x)
using namespace std;
int a[N], s[N], sa[N], cnt[N], rank[N], ran[N], tmp[N], h[N];
int n, p;
void buildSA()
{
	p = 0;
	for (int i = 1; i <= n; i++)
	p = max(p, s[i]);
	for (int i = 1; i <= n; i++)
	cnt[s[i]]++;
	for (int i = 1; i <= p; i++)
	cnt[i] += cnt[i - 1];
	for (int i = n; i >= 1; i--)
	sa[cnt[s[i]]--] = i;
	rank[sa[1]] = p = 1;
	for (int i = 2; i <= n; i++)
	rank[sa[i]] = (s[sa[i]] == s[sa[i - 1]] ? p : ++p);
	for (int k = 1; k <= n; k <<= 1)
	{
		memset(cnt);
		for (int i = 1; i <= n; i++)
		cnt[rank[i + k]]++;
		for (int i = 1; i <= p; i++)
		cnt[i] += cnt[i - 1];
		for (int i = n; i >= 1; i--)
		tmp[cnt[rank[i + k]]--] = i;
		memset(cnt);
		for (int i = 1; i <= n; i++)
		cnt[rank[i]]++;
		for (int i = 1; i <= p; i++)
		cnt[i] += cnt[i - 1];
		for (int i = n; i >= 1; i--)
		sa[cnt[rank[tmp[i]]]--] = tmp[i];
		ran[sa[1]] = p = 1;
		for (int i = 2; i <= n; i++)
		ran[sa[i]] = (rank[sa[i]] == rank[sa[i - 1]] && rank[sa[i] + k] == rank[sa[i - 1] + k] ? p : ++p);
		for (int i = 1; i <= n; i++)
		rank[i] = ran[i];
	}
	for (int i = 1, j = 0; i <= n; i++)
	{
		if (rank[i] == 1) continue;
		while (s[i + j] == s[sa[rank[i] - 1] + j])
		{
			j++;
		}
		h[rank[i]] = j;
		if (j) j--;
	}
}
bool check(int x)
{
	int maxx, minn;
	for (int i = 1; i <= n; i++)
	{
		if (h[i] < x) maxx = minn = sa[i];
		else 
		{
			maxx = max(maxx, sa[i]);
			minn = min(minn, sa[i]);
			if (maxx - minn > x) return 1;
		}
	}
	return 0;
}
int main()
{
	while (1) 
	{
		scanf("%d", &n);
		if (!n) break;
		memset(cnt);
		memset(sa);
		memset(rank);
		memset(ran);
		memset(h);
		memset(tmp);
		memset(a);
		memset(s);
		for (int i = 1; i <= n; i++)
		{
			scanf("%d", &a[i]);
			s[i] = a[i] - a[i - 1] + 100;
		}
		buildSA();
		int l = 0, r = n, mid, ans = 0;
		while (l < r)
		{
			mid = (l + r) / 2;
			if (check(mid)) l = mid + 1, ans = mid;
			else r = mid;
		}
		if (ans >= 4) printf("%d\n", ans + 1);
		else pr(0);
	}
	return 0;
}

总结

在C班学了后,对后缀数组理解了一些,但还没有完全弄清楚,例题也没做多少,可以看看博客之类的

后缀数组的模板要打熟,会用到二分答案之类的

百度百科:后缀数组

翻到的比较好的博客:

后缀数组 最详细讲解

彻底弄懂后缀数组

[知识点] 后缀数组

后缀数组学习笔记【详解|图】

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值