[最优化子串] 字符串(七) {后缀数组的应用(上)}
{
承接上一篇
会构造后缀数组之后
还需要讨论如何应用它
这里分重点分析后缀数组的应用
}
〇.(写在前面)串的连接
一个最为通用的思路就是有几个串
就把几个串连接起来一起生成后缀数组
如果有对某些串有特殊需要
就可以把这些串处理(比如翻转)之后再接上
连接处要加入特殊极小字符 比如"$"
如果串很多就直接加入-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
给出核心代码
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
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信息
给出核心代码:
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
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来记录是否每个串都出现(或者翻转出现)了
给出核心代码:
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
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) 编辑 收藏 举报