后缀数组(一)——hiho120最长可重叠重复K次子串

本人阅读hihocoder题目及讲解后整理此文章

这里写图片描述
这里写图片描述

题目分析

这个问题称为“最长可重叠重复K次子串问题”,所求的是符合要求的所有子串的长度的最大值,这个要求是:子串在字符串中重复出现过至少K次,其中子串可以(部分)重叠。

原文解题方法提示中给出了解决方法,使用后缀数组suffix和一个height数组,并且这两个数组都有高效的求解算法。

后缀数组suffix和height数组

后缀数组:记录所有后缀的数组,并且有序。可用于解决单字符串问题、两个字符串的问题和多个字符串的问题。
e.g. 字符串banana$,($表示字符串结尾),suffix(p)表示从原字符串第p个字符开始到字符串结尾的后缀(后缀p),rank[p]表示后缀 p在所有后缀中从小到大排列的“名次”,排好序的数组记为sa

b a n a n a $
1 2 3 4 5 6 7
i 后缀 suffix(p) sa[i]=p rank[p] height[i]
1 $ 7 rank[7]=1 x
2 a$ 6 rank[6]=2 0
3 ana$ 4 rank[4]=3 1
4 anana$ 2 rank[2]=4 3
5 banana$ 1 rank[1]=5 0
6 na$ 5 rank[5]=6 0
7 nana$ 3 rank[3]=7 2

height数组:令 height[i] 是 suffix(sa[i-1]) 和 suffix(sa[i]) 的最长公共前缀长度,即排名相邻的两个后缀的最长公共前缀长度。比如height[4]就是anana$和ana$的最长公共前缀,也就是ana,长度为3。height也附在上表中.
heigh数组有两个性质,这对于优化height的计算非常有用。

  1. 若 rank[j] < rank[k],则后缀 Sj..n 和 Sk..n 的最长公共前缀为
    min{height[rank[j]+1],height[rank[j]+2]…height[rank[k]]}。
    这个性质是显然的,因为我们已经后缀按字典序排列。
  2. height[rank[i]] ≥ height[rank[i-1]]-1
    选定一个后缀suffix(i-1),它前一个后缀记为suffix(k),则它们的最长公共前缀是height[rank[i-1]]。
    ①若height[rank[i-1]] ≤ 1,则height[rank[i-1]]-1 ≤ 1 - 1 = 0 ≤
    height[rank[i]]。
    ②若 height[rank[i-1]] >1,那么suffix(k+1)将排在suffix(i)的前面,height[rank[i-1]]至少为2,那么suffix(k),suffix(i-1)至少前2个字母是一样的,从第三个字母开始符合字典序,那么suffix(k+1),suffix(i),至少前1个字母是一样的,后面符合字典序,也就是说suffix(k+1)将排在suffix(i)的前面,而且二者的最长公共前缀是height[rank[i-1]]-1。所以suffix(i)和在它前一名的后缀的最长公共前缀至少是height[rank[i-1]]-1(suffix(k+1)和suffix(i)之间可能有其他的后缀,只能使得公共前缀更大或一样)

问题转化

题目要求最长可重叠重复K次子串,有height数组这个问题方便很多。
重复子串即两后缀的公共前缀,最长重复子串,等价于两后缀的最长公共前缀的最大值.(最长公共前缀一定是从相邻的后缀中取得)
求最长可重叠重复K次子串转化为求height 数组中最大的长度为 K的子序列的最小值
原文“小Hi:哈哈!厉害!转化后的这个问题对我来说太容易了,利用单调队列或者二分都可以轻松搞定。”

后缀数组的求解

如果对后缀数组排序,字符串长度为N,数组有N项,使用快排平均要O(N*lgN)次比较,字符串比较时间不是常数,是O(N),总体复杂度为O(N*N*lgN),N较大时方法不使用,倍增算法复杂度是O(N*lgN),DC3的复杂度是O(N)
后缀数组的求法有很多,最有名的是两种倍增算法和DC算法。DC算法时间复杂度更优,但更复杂,倍增算法较实用。
倍增算法思想是:先求出后缀的k-前缀的rank值,然后根据这个值对2k-前缀按照双关键字进行基数排序

倍增算法的步骤:

对长度为 2^0=1 的字符串,也就是所有单字母排序。
用长度为 2^0=1 的字符串,对长度为 2^1=2 的字符串进行双关键字排序。考虑到时间效率,我们一般用基数排序。
用长度为 2^(k-1( 的字符串,对长度为 2^k 的字符串进行双关键字排序。
直到 2^k ≥ n,或者名次数组 Rank 已经从 1 排到 n,得到最终的后缀数组。

height数组的求解

height[rank[i]] ≥ height[rank[i-1]]-1
按照 height[rank[1]], height[rank[2]] … height[rank[n]] 的顺序计算,利用height数组的性质,就可以将时间复杂度可以降为 O(n)。这是因为height数组的值最多不超过n,每次计算结束我们只会减1,所以总的运算不会超过2n次。

面向过程风格实现

void solve()
{
    for (int i = 0; i < 256; i ++) cntA[i] = 0;
    for (int i = 1; i <= n; i ++) cntA[ch[i]] ++;
    for (int i = 1; i < 256; i ++) cntA[i] += cntA[i - 1];
    for (int i = n; i; i --) sa[cntA[ch[i]] --] = i;
    rank[sa[1]] = 1;
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值