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