[最优化子串] 字符串(七) {后缀数组的应用(上)}

{

承接上一篇

构造后缀数组之后

还需要讨论如何应用

这里分重点分析后缀数组的应用

}

 

〇.(写在前面)串的连接

一个最为通用的思路就是有几个串

就把几个串连接起来一起生成后缀数组

如果有对某些串有特殊需要

就可以把这些串处理(比如翻转)之后再接上

连接处要加入特殊极小字符 比如"$"

如果串很多就直接加入-1,-2,-3...

 

一.区间最值(RMQ)

这里我们介绍了Height数组的性质

其中一个最基本的性质就是:

任意两个后缀suffix(j)和suffix(k)的最长公共前缀为

height[rank[j]+1] height[rank[j]+2] height[rank[j]+3]……height[rank[k]]中的最小值

所以为了求任意两个后缀的公共前缀我们一般要运用解决RMQ的一些算法

想了解RMQ的相关信息 请点这里(转载)

(最好能理解稀疏表(ST)的做法 RMQ也可以做到O(N) 不过不推荐)

由于Height是一个静态的数组 不需修改

针对不同的情况 一般用扫描O(N)或者稀疏表O(NLog2N)预处理

然后就能O(1)来回答每个询问了

一般的 如果我们确定要查询哪些区间时就能用扫描解决

如果所查区间没有什么特征的话 就用ST暴力

给出几个具体问题:

Ural 1297 最长回文子串

题意:求一个字符串的最长回文子串

分析:考虑将字符串反转一下与原串相接

那么新串与原串相对应的回文部分的一半就会构成公共前缀

求这些公共前缀中最大的即可

注意分奇偶讨论对称轴应该在哪里

相对应的公共前缀的开始部位在哪里

求公共前缀需要利用ST实现的RMQ

给出核心代码

Get Palindrome
   
   
for i: = 1 to n do
opt[i,
0 ]: = h[i];
z:
= trunc(ln(n) / ln( 2 ));
for j: = 1 to z do
for i: = 1 to n - j + 1 do
begin
x:
= opt[i,j - 1 ];
y:
= opt[i + 1 shl(j - 1 ),j - 1 ];
if x < y
then opt[i,j]: = x
else opt[i,j]: = y;
end ;
n:
= m - 1 ;
for i: = 1 to n do
begin
x:
= r[i];
y:
= r[m * 2 - i];
if x > y
then begin
x:
= x xor y;
y:
= x xor y;
x:
= x xor y;
end ;
inc(x);
k:
= trunc(ln(y - x + 1 ) / ln( 2 ));
z:
= opt[y - 1 shl k + 1 ,k];
if opt[x,k] < z
then z: = opt[x,k];
z:
= z * 2 - 1 ;
if z > ans
then begin
add:
= i;
ans:
= z;
end ;
end ;
for i: = 1 to n do
begin
x:
= r[i];
y:
= r[m * 2 - i + 1 ];
if x > y
then begin
x:
= x xor y;
y:
= x xor y;
x:
= x xor y;
end ;
inc(x);
k:
= trunc(ln(y - x + 1 ) / ln( 2 ));
z:
= opt[y - 1 shl k + 1 ,k];
if opt[x,k] < z
then z: = opt[x,k];
z:
= z * 2 ;
if z > ans
then begin
add:
= i;
ans:
= z;
end ;
end ;
writeln(ans);

Usaco 2010Dec Letter 连续最长匹配

题意:给定主串A和模式串B

求模式串最少断为几块 可以让分串全部是主串的子串

分析:首先要知道一个基本贪心算法

每次从剩余串头部砍掉一个与主串匹配的最长串 就能得到最优解

证明利用反证法即可

可以运用KMP算法暴力 复杂度O(N^2)

显然是不行的 考虑将两个串连接起来求出后缀数组

B串的每个后缀都出现过 所以所有B串的断点暴露出来

我们只要算出每个断点处最长能匹配到多远

然后让断点指针从B的第一个字符开始

通过已经求好的当前断点的最长匹配 跳到下一个断点就行了

具体如何计算B的每个断点向后匹配到远可以有两种做法:

(参考下面的图示)

1 A串开始的后缀

2 A串开始的后缀 <-上面最近的A串

3 B串开始的后缀

4 B串开始的后缀

5 B串开始的后缀 <-当前B串

6 B串开始的后缀

7 B串开始的后缀

8 A串开始的后缀 <-下面最近的A串

9 B串开始的后缀

显然每个B串开始的后缀都代表一个断点

考虑它和向上向下最近的A串开始的后缀之间Height[]的RMQ即可

由于需要RMQ的区间都已知 可以用扫描来解决

我当时写的是ST 代码也相对好理解一点

扫描出Up[]和Down[]的时候可以顺便得到我们要知道RMQ信息

给出核心代码:

Up-Down-RMQ
   
   
temp: = trunc(ln(n) / ln( 2 ));
for i: = 1 to n do
opt[i,
0 ]: = h[i];
for j: = 1 to temp do
for i: = 1 to n - 1 shl j + 1 do
if opt[i,j - 1 ] < opt[i + 1 shl(j - 1 ),j - 1 ]
then opt[i,j]: = opt[i,j - 1 ]
else opt[i,j]: = opt[i + 1 shl(j - 1 ),j - 1 ];
for i: = 1 to n do
if s[i] <= m
then b[i]: = 1
else b[i]: = 2 ;
j:
= 0 ;
for i: = 1 to n do
if b[i] = 1
then j: = i
else up[i]: = j;
j:
= n + 1 ;
for i: = n downto 1 do
if b[i] = 1
then j: = i
else down[i]: = j;
j:
= m + 2 ; ans: = 0 ;
while j < n do
begin
inc(ans);
x:
= RMQ(up[r[j]] + 1 ,r[j]);
y:
= RMQ(r[j] + 1 ,down[r[j]]);
if x > y
then j: = j + x
else j: = j + y;
end ;
writeln(ans);

 

二.二分答案+Height分组

通过二分答案Ans将问题转化为判定性问题

是非常常用的做法 通用性很好

与之配合 我们还会把Height分组 保证每组内的后缀的公共前缀都>=Ans

理论依据还是Height数组的基本性质

然后考虑每组内是否存在合法的解

给出两个具体问题:

Pku 1743 最长重复不重叠子串

题意:求一段音乐的主旋律(此题意境甚好)

分析:将原序列 差分一次 转化为最长重复不重叠子串

然后二分答案 并将Height分组考虑每组内是否存在不重叠的子串

具体判定只要看最早开始后缀和最晚开始后缀的位置差

[最优化子串] 字符串(七) {后缀数组的应用(上)}_第1张图片

Pku 3261 最长重复K次可重叠子串

题意:如上所述

分析:基本思路同上

判定每组时改为判定是否存在K个后缀即可

Pku 3294 出现或反转后出现在每个字符串中的最长子串

题意:如上所述 

分析:先将所有串正反拼接起来 生成后缀数组

然后二分答案 Height分组

用一个简单Hash来记录是否每个串都出现(或者翻转出现)

给出核心代码:

Hash-Group
   
   
x: = 0 ; y: = max; t: = 0 ; tt: = 0 ; max: =- 1 ;
while x <= y do
begin
mid:
= (x + y) div 2 ;
flag:
= false;
for i: = 1 to n - 1 do
begin
if hash[id[sa[i]]] = 0
then begin hash[id[sa[i]]]: = 1 ; inc(t); end ;
if h[i + 1 ] < mid
then begin
if 2 * t > m
then begin
if mid > max
then begin flag: = true; max: = mid; tt: = 0 ; end ;
inc(tt);
for j: = sa[i - 1 ] to sa[i - 1 ] + mid - 1 do
ans[tt][j
- sa[i - 1 ] + 1 ]: = ch[j];
end ;
fillchar(hash,sizeof(hash),
0 );
t:
= 0 ;
end ;
end ;
if flag then x: = mid + 1 else y: = mid - 1 ;
end ;
if max =- 1 then writeln( ' ? ' )
else for i: = 1 to tt do
begin
for j: = 1 to max do
write(ans[i][j]);
writeln;
end ;
readln(m);
if m <> 0 then writeln;

Spoj 220 两次出现在每个字符串中的最长子串

 

题意:如上所述

分析:拼接所有串 二分答案 Height分组

简单Hash记录次数 看每组是否每个串都出现2次

 

先写到这里 下半部分讨论Height分析

难度比较大

 

Bob Han 原创 转载请注明出处 http://www.cnblogs.com/Booble/

你可能感兴趣的:(后缀数组)