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

{

承接上一篇

构造后缀数组之后

还需要讨论如何应用

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

}

 

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

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

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

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

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

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

如果串很多就直接加入-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分组考虑每组内是否存在不重叠的子串

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

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/

posted on 2010-12-12 23:01  Master_Chivu  阅读(2432)  评论(0编辑  收藏  举报

导航